Merge pull request #10531 from strapi/chore/move-cm-in-core
Move Content manager in @strapi/admin
@ -6,7 +6,7 @@
|
|||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"draftAndPublish": false,
|
"draftAndPublish": true,
|
||||||
"increments": true,
|
"increments": true,
|
||||||
"timestamps": [
|
"timestamps": [
|
||||||
"created_at",
|
"created_at",
|
||||||
@ -86,7 +86,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"slug": {
|
"slug": {
|
||||||
"type": "uid"
|
"type": "uid",
|
||||||
|
"targetField": "city"
|
||||||
},
|
},
|
||||||
"notrepeat_req": {
|
"notrepeat_req": {
|
||||||
"type": "component",
|
"type": "component",
|
||||||
|
|||||||
@ -0,0 +1,483 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const addressCT = {
|
||||||
|
uid: 'application::address.address',
|
||||||
|
settings: {
|
||||||
|
bulkable: true,
|
||||||
|
filterable: true,
|
||||||
|
searchable: true,
|
||||||
|
pageSize: 10,
|
||||||
|
mainField: 'postal_coder',
|
||||||
|
defaultSortBy: 'postal_coder',
|
||||||
|
defaultSortOrder: 'ASC',
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
id: { edit: {}, list: { label: 'Id', searchable: true, sortable: true } },
|
||||||
|
postal_coder: {
|
||||||
|
edit: {
|
||||||
|
label: 'Postal_coder',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Postal_coder', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
list: {
|
||||||
|
label: 'Categories',
|
||||||
|
searchable: false,
|
||||||
|
sortable: false,
|
||||||
|
mainField: { name: 'name', schema: { type: 'string' } },
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
label: 'Categories',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
mainField: { name: 'name', schema: { type: 'string' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cover: {
|
||||||
|
edit: { label: 'Cover', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Cover', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
edit: { label: 'Images', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Images', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
edit: { label: 'City', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'City', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
likes: {
|
||||||
|
list: {
|
||||||
|
label: 'Likes',
|
||||||
|
searchable: false,
|
||||||
|
sortable: false,
|
||||||
|
mainField: { name: 'id', schema: { type: 'integer' } },
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
label: 'Likes',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
mainField: { name: 'id', schema: { type: 'integer' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
edit: { label: 'Json', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Json', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
slug: {
|
||||||
|
edit: { label: 'Slug', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Slug', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
notrepeat_req: {
|
||||||
|
edit: {
|
||||||
|
label: 'Notrepeat_req',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Notrepeat_req', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
repeat_req: {
|
||||||
|
edit: {
|
||||||
|
label: 'Repeat_req',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Repeat_req', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
repeat_req_min: {
|
||||||
|
edit: {
|
||||||
|
label: 'Repeat_req_min',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Repeat_req_min', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
edit: {
|
||||||
|
label: 'Created_at',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: false,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Created_at', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
edit: {
|
||||||
|
label: 'Updated_at',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: false,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Updated_at', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layouts: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
key: '__id_key__',
|
||||||
|
name: 'id',
|
||||||
|
fieldSchema: { type: 'integer' },
|
||||||
|
metadatas: { label: 'Id', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '__postal_coder_key__',
|
||||||
|
name: 'postal_coder',
|
||||||
|
fieldSchema: { type: 'string', pluginOptions: { i18n: { localized: true } } },
|
||||||
|
metadatas: { label: 'Postal_coder', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '__categories_key__',
|
||||||
|
name: 'categories',
|
||||||
|
fieldSchema: {
|
||||||
|
collection: 'category',
|
||||||
|
via: 'addresses',
|
||||||
|
dominant: true,
|
||||||
|
attribute: 'category',
|
||||||
|
column: 'id',
|
||||||
|
isVirtual: true,
|
||||||
|
type: 'relation',
|
||||||
|
targetModel: 'application::category.category',
|
||||||
|
relationType: 'manyToMany',
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Categories',
|
||||||
|
searchable: false,
|
||||||
|
sortable: false,
|
||||||
|
mainField: { name: 'name', schema: { type: 'string' } },
|
||||||
|
},
|
||||||
|
queryInfos: {
|
||||||
|
endPoint: 'collection-types/application::address.address',
|
||||||
|
defaultParams: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '__cover_key__',
|
||||||
|
name: 'cover',
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'media',
|
||||||
|
multiple: false,
|
||||||
|
required: false,
|
||||||
|
allowedTypes: ['files', 'images', 'videos'],
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
},
|
||||||
|
metadatas: { label: 'Cover', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edit: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'postal_coder',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: { type: 'string', pluginOptions: { i18n: { localized: true } } },
|
||||||
|
metadatas: {
|
||||||
|
label: 'Postal_coder',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cover',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'media',
|
||||||
|
multiple: false,
|
||||||
|
required: false,
|
||||||
|
allowedTypes: ['files', 'images', 'videos'],
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Cover',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'images',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'media',
|
||||||
|
multiple: true,
|
||||||
|
required: false,
|
||||||
|
allowedTypes: ['images'],
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Images',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'city',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
maxLength: 200,
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'City',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'json',
|
||||||
|
size: 12,
|
||||||
|
fieldSchema: { type: 'json', pluginOptions: { i18n: { localized: true } } },
|
||||||
|
metadatas: {
|
||||||
|
label: 'Json',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'slug',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: { type: 'uid', targetField: 'city' },
|
||||||
|
metadatas: {
|
||||||
|
label: 'Slug',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'notrepeat_req',
|
||||||
|
size: 12,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: false,
|
||||||
|
pluginOptions: { i18n: { localized: false } },
|
||||||
|
component: 'blog.test-como',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Notrepeat_req',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'repeat_req',
|
||||||
|
size: 12,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: true,
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
component: 'blog.test-como',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Repeat_req',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'repeat_req_min',
|
||||||
|
size: 12,
|
||||||
|
fieldSchema: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: true,
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
component: 'blog.test-como',
|
||||||
|
required: false,
|
||||||
|
min: 2,
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Repeat_req_min',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
editRelations: [
|
||||||
|
{
|
||||||
|
name: 'categories',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
collection: 'category',
|
||||||
|
via: 'addresses',
|
||||||
|
dominant: true,
|
||||||
|
attribute: 'category',
|
||||||
|
column: 'id',
|
||||||
|
isVirtual: true,
|
||||||
|
type: 'relation',
|
||||||
|
targetModel: 'application::category.category',
|
||||||
|
relationType: 'manyToMany',
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Categories',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
mainField: { name: 'name', schema: { type: 'string' } },
|
||||||
|
},
|
||||||
|
queryInfos: {
|
||||||
|
endPoint: '/content-manager/relations/application::address.address/categories',
|
||||||
|
containsKey: 'name_contains',
|
||||||
|
defaultParams: {},
|
||||||
|
shouldDisplayRelationLink: true,
|
||||||
|
},
|
||||||
|
targetModelPluginOptions: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'likes',
|
||||||
|
size: 6,
|
||||||
|
fieldSchema: {
|
||||||
|
collection: 'like',
|
||||||
|
via: 'address',
|
||||||
|
isVirtual: true,
|
||||||
|
type: 'relation',
|
||||||
|
targetModel: 'application::like.like',
|
||||||
|
relationType: 'oneToMany',
|
||||||
|
},
|
||||||
|
metadatas: {
|
||||||
|
label: 'Likes',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
mainField: { name: 'id', schema: { type: 'integer' } },
|
||||||
|
},
|
||||||
|
queryInfos: {
|
||||||
|
endPoint: '/content-manager/relations/application::address.address/likes',
|
||||||
|
containsKey: 'id_contains',
|
||||||
|
defaultParams: {},
|
||||||
|
shouldDisplayRelationLink: true,
|
||||||
|
},
|
||||||
|
targetModelPluginOptions: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
isDisplayed: true,
|
||||||
|
apiID: 'address',
|
||||||
|
kind: 'collectionType',
|
||||||
|
info: { name: 'address', description: '', label: 'Addresses' },
|
||||||
|
options: {
|
||||||
|
draftAndPublish: true,
|
||||||
|
increments: true,
|
||||||
|
timestamps: ['created_at', 'updated_at'],
|
||||||
|
comment: '',
|
||||||
|
},
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
attributes: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
postal_coder: { type: 'string', pluginOptions: { i18n: { localized: true } } },
|
||||||
|
categories: {
|
||||||
|
collection: 'category',
|
||||||
|
via: 'addresses',
|
||||||
|
dominant: true,
|
||||||
|
attribute: 'category',
|
||||||
|
column: 'id',
|
||||||
|
isVirtual: true,
|
||||||
|
type: 'relation',
|
||||||
|
targetModel: 'application::category.category',
|
||||||
|
relationType: 'manyToMany',
|
||||||
|
},
|
||||||
|
cover: {
|
||||||
|
type: 'media',
|
||||||
|
multiple: false,
|
||||||
|
required: false,
|
||||||
|
allowedTypes: ['files', 'images', 'videos'],
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
type: 'media',
|
||||||
|
multiple: true,
|
||||||
|
required: false,
|
||||||
|
allowedTypes: ['images'],
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
maxLength: 200,
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
},
|
||||||
|
likes: {
|
||||||
|
collection: 'like',
|
||||||
|
via: 'address',
|
||||||
|
isVirtual: true,
|
||||||
|
type: 'relation',
|
||||||
|
targetModel: 'application::like.like',
|
||||||
|
relationType: 'oneToMany',
|
||||||
|
},
|
||||||
|
json: { type: 'json', pluginOptions: { i18n: { localized: true } } },
|
||||||
|
slug: { type: 'uid', targetField: 'city' },
|
||||||
|
notrepeat_req: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: false,
|
||||||
|
pluginOptions: { i18n: { localized: false } },
|
||||||
|
component: 'blog.test-como',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
repeat_req: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: true,
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
component: 'blog.test-como',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
repeat_req_min: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: true,
|
||||||
|
pluginOptions: { i18n: { localized: true } },
|
||||||
|
component: 'blog.test-como',
|
||||||
|
required: false,
|
||||||
|
min: 2,
|
||||||
|
},
|
||||||
|
created_at: { type: 'timestamp' },
|
||||||
|
updated_at: { type: 'timestamp' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addressCT;
|
||||||
101
packages/admin-test-utils/lib/fixtures/metaData/address.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const addressMetaData = {
|
||||||
|
id: { edit: {}, list: { label: 'Id', searchable: true, sortable: true } },
|
||||||
|
postal_coder: {
|
||||||
|
edit: {
|
||||||
|
label: 'Postal_coder',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Postal_coder', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
list: {
|
||||||
|
label: 'Categories',
|
||||||
|
searchable: false,
|
||||||
|
sortable: false,
|
||||||
|
mainField: { name: 'name', schema: { type: 'string' } },
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
label: 'Categories',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
mainField: { name: 'name', schema: { type: 'string' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cover: {
|
||||||
|
edit: { label: 'Cover', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Cover', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
edit: { label: 'Images', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Images', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
city: {
|
||||||
|
edit: { label: 'City', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'City', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
likes: {
|
||||||
|
list: {
|
||||||
|
label: 'Likes',
|
||||||
|
searchable: false,
|
||||||
|
sortable: false,
|
||||||
|
mainField: { name: 'id', schema: { type: 'integer' } },
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
label: 'Likes',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
mainField: { name: 'id', schema: { type: 'integer' } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
edit: { label: 'Json', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Json', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
slug: {
|
||||||
|
edit: { label: 'Slug', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Slug', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
notrepeat_req: {
|
||||||
|
edit: {
|
||||||
|
label: 'Notrepeat_req',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Notrepeat_req', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
repeat_req: {
|
||||||
|
edit: { label: 'Repeat_req', description: '', placeholder: '', visible: true, editable: true },
|
||||||
|
list: { label: 'Repeat_req', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
repeat_req_min: {
|
||||||
|
edit: {
|
||||||
|
label: 'Repeat_req_min',
|
||||||
|
description: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: true,
|
||||||
|
editable: true,
|
||||||
|
},
|
||||||
|
list: { label: 'Repeat_req_min', searchable: false, sortable: false },
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
edit: { label: 'Created_at', description: '', placeholder: '', visible: false, editable: true },
|
||||||
|
list: { label: 'Created_at', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
edit: { label: 'Updated_at', description: '', placeholder: '', visible: false, editable: true },
|
||||||
|
list: { label: 'Updated_at', searchable: true, sortable: true },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addressMetaData;
|
||||||
@ -4,6 +4,37 @@
|
|||||||
const { combineReducers, createStore } = require('redux');
|
const { combineReducers, createStore } = require('redux');
|
||||||
|
|
||||||
const reducers = {
|
const reducers = {
|
||||||
|
'content-manager_app': jest.fn(() => ({
|
||||||
|
components: [],
|
||||||
|
status: 'loading',
|
||||||
|
models: [],
|
||||||
|
collectionTypeLinks: [],
|
||||||
|
singleTypeLinks: [],
|
||||||
|
})),
|
||||||
|
'content-manager_listView': jest.fn(() => ({
|
||||||
|
data: [],
|
||||||
|
didDeleteData: false,
|
||||||
|
entriesToDelete: [],
|
||||||
|
isLoading: true,
|
||||||
|
showModalConfirmButtonLoading: false,
|
||||||
|
showWarningDelete: false,
|
||||||
|
showWarningDeleteAll: false,
|
||||||
|
contentType: {},
|
||||||
|
initialDisplayedHeaders: [],
|
||||||
|
displayedHeaders: [],
|
||||||
|
pagination: {
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
'content-manager_rbacManager': jest.fn(() => ({ permissions: null })),
|
||||||
|
'content-manager_editViewLayoutManager': jest.fn(() => ({ currentLayout: null })),
|
||||||
|
'content-manager_editViewCrudReducer': jest.fn(() => ({
|
||||||
|
componentsDataStructure: {},
|
||||||
|
contentTypeDataStructure: {},
|
||||||
|
isLoading: true,
|
||||||
|
data: {},
|
||||||
|
status: 'resolved',
|
||||||
|
})),
|
||||||
rbacProvider: jest.fn(() => ({ allPermissions: null, collectionTypesRelatedPermissions: {} })),
|
rbacProvider: jest.fn(() => ({ allPermissions: null, collectionTypesRelatedPermissions: {} })),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
/* eslint-disable import/extensions */
|
||||||
const commander = require('commander');
|
const commander = require('commander');
|
||||||
const generateNewApp = require('@strapi/generate-new');
|
const generateNewApp = require('@strapi/generate-new');
|
||||||
const promptUser = require('./utils/prompt-user');
|
const promptUser = require('./utils/prompt-user');
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// FIXME
|
||||||
|
/* eslint-disable import/extensions */
|
||||||
const commander = require('commander');
|
const commander = require('commander');
|
||||||
|
|
||||||
const packageJson = require('./package.json');
|
const packageJson = require('./package.json');
|
||||||
|
|||||||
@ -1,37 +1,27 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { QueryClientProvider, QueryClient } from 'react-query';
|
|
||||||
import { ThemeProvider } from 'styled-components';
|
|
||||||
import { LibraryProvider, StrapiAppProvider } from '@strapi/helper-plugin';
|
|
||||||
import pick from 'lodash/pick';
|
import pick from 'lodash/pick';
|
||||||
import createHook from '@strapi/hooks';
|
|
||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
|
import { basename, createHook } from './core/utils';
|
||||||
import configureStore from './core/store/configureStore';
|
import configureStore from './core/store/configureStore';
|
||||||
import { Plugin } from './core/apis';
|
import { Plugin } from './core/apis';
|
||||||
import basename from './utils/basename';
|
|
||||||
import App from './pages/App';
|
import App from './pages/App';
|
||||||
import LanguageProvider from './components/LanguageProvider';
|
import Providers from './components/Providers';
|
||||||
import AutoReloadOverlayBlockerProvider from './components/AutoReloadOverlayBlockerProvider';
|
import Theme from './components/Theme';
|
||||||
import OverlayBlocker from './components/OverlayBlocker';
|
|
||||||
import Fonts from './components/Fonts';
|
|
||||||
import GlobalStyle from './components/GlobalStyle';
|
|
||||||
import Notifications from './components/Notifications';
|
|
||||||
import themes from './themes';
|
|
||||||
import languageNativeNames from './translations/languageNativeNames';
|
import languageNativeNames from './translations/languageNativeNames';
|
||||||
|
import {
|
||||||
|
INJECT_COLUMN_IN_TABLE,
|
||||||
|
MUTATE_COLLECTION_TYPES_LINKS,
|
||||||
|
MUTATE_EDIT_VIEW_LAYOUT,
|
||||||
|
MUTATE_SINGLE_TYPES_LINKS,
|
||||||
|
} from './exposedHooks';
|
||||||
|
import injectionZones from './injectionZones';
|
||||||
|
import themes from './themes';
|
||||||
|
|
||||||
window.strapi = {
|
window.strapi = {
|
||||||
backendURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
backendURL: process.env.STRAPI_ADMIN_BACKEND_URL,
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
class StrapiApp {
|
class StrapiApp {
|
||||||
constructor({ appPlugins, library, locales, middlewares, reducers }) {
|
constructor({ appPlugins, library, locales, middlewares, reducers }) {
|
||||||
this.appLocales = ['en', ...locales.filter(loc => loc !== 'en')];
|
this.appLocales = ['en', ...locales.filter(loc => loc !== 'en')];
|
||||||
@ -42,6 +32,10 @@ class StrapiApp {
|
|||||||
this.reducers = reducers;
|
this.reducers = reducers;
|
||||||
this.translations = {};
|
this.translations = {};
|
||||||
this.hooksDict = {};
|
this.hooksDict = {};
|
||||||
|
this.admin = {
|
||||||
|
injectionZones,
|
||||||
|
};
|
||||||
|
|
||||||
this.menu = [];
|
this.menu = [];
|
||||||
this.settings = {
|
this.settings = {
|
||||||
global: {
|
global: {
|
||||||
@ -72,9 +66,7 @@ class StrapiApp {
|
|||||||
`Expected link.to to be a string instead received ${typeof link.to}`
|
`Expected link.to to be a string instead received ${typeof link.to}`
|
||||||
);
|
);
|
||||||
invariant(
|
invariant(
|
||||||
['/plugins/content-manager', '/plugins/content-type-builder', '/plugins/upload'].includes(
|
['/plugins/content-type-builder', '/plugins/upload'].includes(link.to),
|
||||||
link.to
|
|
||||||
),
|
|
||||||
'This method is not available for your plugin'
|
'This method is not available for your plugin'
|
||||||
);
|
);
|
||||||
invariant(
|
invariant(
|
||||||
@ -153,21 +145,6 @@ class StrapiApp {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async initialize() {
|
|
||||||
Object.keys(this.appPlugins).forEach(plugin => {
|
|
||||||
this.appPlugins[plugin].register({
|
|
||||||
addComponents: this.addComponents,
|
|
||||||
addCorePluginMenuLink: this.addCorePluginMenuLink,
|
|
||||||
addFields: this.addFields,
|
|
||||||
addMenuLink: this.addMenuLink,
|
|
||||||
addMiddlewares: this.addMiddlewares,
|
|
||||||
addReducers: this.addReducers,
|
|
||||||
createSettingSection: this.createSettingSection,
|
|
||||||
registerPlugin: this.registerPlugin,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async boot() {
|
async boot() {
|
||||||
Object.keys(this.appPlugins).forEach(plugin => {
|
Object.keys(this.appPlugins).forEach(plugin => {
|
||||||
const boot = this.appPlugins[plugin].boot;
|
const boot = this.appPlugins[plugin].boot;
|
||||||
@ -177,11 +154,28 @@ class StrapiApp {
|
|||||||
addSettingsLink: this.addSettingsLink,
|
addSettingsLink: this.addSettingsLink,
|
||||||
addSettingsLinks: this.addSettingsLinks,
|
addSettingsLinks: this.addSettingsLinks,
|
||||||
getPlugin: this.getPlugin,
|
getPlugin: this.getPlugin,
|
||||||
|
injectContentManagerComponent: this.injectContentManagerComponent,
|
||||||
|
registerHook: this.registerHook,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bootstrapAdmin = async () => {
|
||||||
|
this.createHook(INJECT_COLUMN_IN_TABLE);
|
||||||
|
this.createHook(MUTATE_COLLECTION_TYPES_LINKS);
|
||||||
|
this.createHook(MUTATE_SINGLE_TYPES_LINKS);
|
||||||
|
this.createHook(MUTATE_EDIT_VIEW_LAYOUT);
|
||||||
|
|
||||||
|
await this.loadAdminTrads();
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
createHook = name => {
|
||||||
|
this.hooksDict[name] = createHook();
|
||||||
|
};
|
||||||
|
|
||||||
createSettingSection = (section, links) => {
|
createSettingSection = (section, links) => {
|
||||||
invariant(section.id, 'section.id should be defined');
|
invariant(section.id, 'section.id should be defined');
|
||||||
invariant(
|
invariant(
|
||||||
@ -205,10 +199,46 @@ class StrapiApp {
|
|||||||
return store;
|
return store;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getAdminInjectedComponents = (moduleName, containerName, blockName) => {
|
||||||
|
try {
|
||||||
|
return this.admin.injectionZones[moduleName][containerName][blockName] || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Cannot get injected component', err);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getPlugin = pluginId => {
|
getPlugin = pluginId => {
|
||||||
return this.plugins[pluginId];
|
return this.plugins[pluginId];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
Object.keys(this.appPlugins).forEach(plugin => {
|
||||||
|
this.appPlugins[plugin].register({
|
||||||
|
addComponents: this.addComponents,
|
||||||
|
addCorePluginMenuLink: this.addCorePluginMenuLink,
|
||||||
|
addFields: this.addFields,
|
||||||
|
addMenuLink: this.addMenuLink,
|
||||||
|
addMiddlewares: this.addMiddlewares,
|
||||||
|
addReducers: this.addReducers,
|
||||||
|
createHook: this.createHook,
|
||||||
|
createSettingSection: this.createSettingSection,
|
||||||
|
registerPlugin: this.registerPlugin,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
injectContentManagerComponent = (containerName, blockName, component) => {
|
||||||
|
invariant(
|
||||||
|
this.admin.injectionZones.contentManager[containerName]?.[blockName],
|
||||||
|
`The ${containerName} ${blockName} zone is not defined in the content manager`
|
||||||
|
);
|
||||||
|
invariant(component, 'A Component must be provided');
|
||||||
|
|
||||||
|
this.admin.injectionZones.contentManager[containerName][blockName].push(component);
|
||||||
|
};
|
||||||
|
|
||||||
async loadAdminTrads() {
|
async loadAdminTrads() {
|
||||||
const arrayOfPromises = this.appLocales.map(locale => {
|
const arrayOfPromises = this.appLocales.map(locale => {
|
||||||
return import(/* webpackChunkName: "[request]" */ `./translations/${locale}.json`)
|
return import(/* webpackChunkName: "[request]" */ `./translations/${locale}.json`)
|
||||||
@ -270,27 +300,28 @@ class StrapiApp {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerHook = (name, fn) => {
|
||||||
|
invariant(
|
||||||
|
this.hooksDict[name],
|
||||||
|
`The hook ${name} is not defined. You are trying to register a hook that does not exist in the application.`
|
||||||
|
);
|
||||||
|
this.hooksDict[name].register(fn);
|
||||||
|
};
|
||||||
|
|
||||||
registerPlugin = pluginConf => {
|
registerPlugin = pluginConf => {
|
||||||
const plugin = Plugin(pluginConf);
|
const plugin = Plugin(pluginConf);
|
||||||
|
|
||||||
this.plugins[plugin.pluginId] = plugin;
|
this.plugins[plugin.pluginId] = plugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
createHook = name => {
|
|
||||||
this.hooksDict[name] = createHook();
|
|
||||||
};
|
|
||||||
|
|
||||||
registerHook = (name, fn) => {
|
|
||||||
this.hooksDict[name].register(fn);
|
|
||||||
};
|
|
||||||
|
|
||||||
runHookSeries = (name, asynchronous = false) =>
|
runHookSeries = (name, asynchronous = false) =>
|
||||||
asynchronous ? this.hooksDict[name].runSeriesAsync() : this.hooksDict[name].runSeries();
|
asynchronous ? this.hooksDict[name].runSeriesAsync() : this.hooksDict[name].runSeries();
|
||||||
|
|
||||||
runHookWaterfall = (name, initialValue, asynchronous = false) =>
|
runHookWaterfall = (name, initialValue, asynchronous = false, store) => {
|
||||||
asynchronous
|
return asynchronous
|
||||||
? this.hooksDict[name].runWaterfallAsync(initialValue)
|
? this.hooksDict[name].runWaterfallAsync(initialValue, store)
|
||||||
: this.hooksDict[name].runWaterfall(initialValue);
|
: this.hooksDict[name].runWaterfall(initialValue, store);
|
||||||
|
};
|
||||||
|
|
||||||
runHookParallel = name => this.hooksDict[name].runParallel();
|
runHookParallel = name => this.hooksDict[name].runParallel();
|
||||||
|
|
||||||
@ -304,37 +335,29 @@ class StrapiApp {
|
|||||||
} = this.library;
|
} = this.library;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<Theme theme={themes}>
|
||||||
<ThemeProvider theme={themes}>
|
<Providers
|
||||||
<GlobalStyle />
|
components={components}
|
||||||
<Fonts />
|
fields={fields}
|
||||||
<Provider store={store}>
|
localeNames={localeNames}
|
||||||
<StrapiAppProvider
|
getAdminInjectedComponents={this.getAdminInjectedComponents}
|
||||||
getPlugin={this.getPlugin}
|
getPlugin={this.getPlugin}
|
||||||
menu={this.menu}
|
messages={this.translations}
|
||||||
plugins={this.plugins}
|
menu={this.menu}
|
||||||
runHookParallel={this.runHookParallel}
|
plugins={this.plugins}
|
||||||
runHookWaterfall={this.runHookWaterfall}
|
runHookParallel={this.runHookParallel}
|
||||||
runHookSeries={this.runHookSeries}
|
runHookWaterfall={(name, initialValue, async = false) => {
|
||||||
settings={this.settings}
|
return this.runHookWaterfall(name, initialValue, async, store);
|
||||||
>
|
}}
|
||||||
<LibraryProvider components={components} fields={fields}>
|
runHookSeries={this.runHookSeries}
|
||||||
<LanguageProvider messages={this.translations} localeNames={localeNames}>
|
settings={this.settings}
|
||||||
<AutoReloadOverlayBlockerProvider>
|
store={store}
|
||||||
<OverlayBlocker>
|
>
|
||||||
<Notifications>
|
<BrowserRouter basename={basename}>
|
||||||
<BrowserRouter basename={basename}>
|
<App store={store} />
|
||||||
<App store={store} />
|
</BrowserRouter>
|
||||||
</BrowserRouter>
|
</Providers>
|
||||||
</Notifications>
|
</Theme>
|
||||||
</OverlayBlocker>
|
|
||||||
</AutoReloadOverlayBlockerProvider>
|
|
||||||
</LanguageProvider>
|
|
||||||
</LibraryProvider>
|
|
||||||
</StrapiAppProvider>
|
|
||||||
</Provider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import axiosInstance from '../../../utils/axiosInstance';
|
import { axiosInstance } from '../../../core/utils';
|
||||||
import packageJSON from '../../../../../package.json';
|
import packageJSON from '../../../../../package.json';
|
||||||
|
|
||||||
const strapiVersion = packageJSON.version;
|
const strapiVersion = packageJSON.version;
|
||||||
|
|||||||
@ -1,14 +1,29 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { BaselineAlignment } from '@strapi/helper-plugin';
|
||||||
import { Footer, Header, LinksContainer, LinksSection, SectionTitle } from './compos';
|
import { Footer, Header, LinksContainer, LinksSection, SectionTitle } from './compos';
|
||||||
|
import LeftMenuLink from './compos/Link';
|
||||||
|
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
|
|
||||||
const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
const LeftMenu = ({ generalSectionLinks, pluginsSectionLinks }) => {
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<LinksContainer>
|
<LinksContainer>
|
||||||
|
<BaselineAlignment top size="16px" />
|
||||||
|
<LeftMenuLink
|
||||||
|
to="/content-manager"
|
||||||
|
icon="book-open"
|
||||||
|
intlLabel={{
|
||||||
|
id: `content-manager.plugin.name`,
|
||||||
|
defaultMessage: 'Content manager',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<BaselineAlignment bottom size="2px" />
|
||||||
|
|
||||||
{pluginsSectionLinks.length > 0 && (
|
{pluginsSectionLinks.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<SectionTitle>
|
<SectionTitle>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { memo } from 'react';
|
|||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import favicon from '../../favicon.png';
|
import favicon from '../../favicon.ico';
|
||||||
|
|
||||||
const PageTitle = ({ title }) => {
|
const PageTitle = ({ title }) => {
|
||||||
return <Helmet title={title} link={[{ rel: 'icon', type: 'image/png', href: favicon }]} />;
|
return <Helmet title={title} link={[{ rel: 'icon', type: 'image/png', href: favicon }]} />;
|
||||||
|
|||||||
93
packages/core/admin/admin/src/components/Providers/index.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { QueryClientProvider, QueryClient } from 'react-query';
|
||||||
|
import { LibraryProvider, StrapiAppProvider } from '@strapi/helper-plugin';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { AdminContext } from '../../contexts';
|
||||||
|
import LanguageProvider from '../LanguageProvider';
|
||||||
|
import AutoReloadOverlayBlockerProvider from '../AutoReloadOverlayBlockerProvider';
|
||||||
|
import Notifications from '../Notifications';
|
||||||
|
import OverlayBlocker from '../OverlayBlocker';
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Providers = ({
|
||||||
|
children,
|
||||||
|
components,
|
||||||
|
fields,
|
||||||
|
getAdminInjectedComponents,
|
||||||
|
getPlugin,
|
||||||
|
localeNames,
|
||||||
|
menu,
|
||||||
|
messages,
|
||||||
|
plugins,
|
||||||
|
runHookParallel,
|
||||||
|
runHookSeries,
|
||||||
|
runHookWaterfall,
|
||||||
|
settings,
|
||||||
|
store,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<Provider store={store}>
|
||||||
|
<AdminContext.Provider value={{ getAdminInjectedComponents }}>
|
||||||
|
<StrapiAppProvider
|
||||||
|
getPlugin={getPlugin}
|
||||||
|
menu={menu}
|
||||||
|
plugins={plugins}
|
||||||
|
runHookParallel={runHookParallel}
|
||||||
|
runHookWaterfall={runHookWaterfall}
|
||||||
|
runHookSeries={runHookSeries}
|
||||||
|
settings={settings}
|
||||||
|
>
|
||||||
|
<LibraryProvider components={components} fields={fields}>
|
||||||
|
<LanguageProvider messages={messages} localeNames={localeNames}>
|
||||||
|
<AutoReloadOverlayBlockerProvider>
|
||||||
|
<OverlayBlocker>
|
||||||
|
<Notifications>{children}</Notifications>
|
||||||
|
</OverlayBlocker>
|
||||||
|
</AutoReloadOverlayBlockerProvider>
|
||||||
|
</LanguageProvider>
|
||||||
|
</LibraryProvider>
|
||||||
|
</StrapiAppProvider>
|
||||||
|
</AdminContext.Provider>
|
||||||
|
</Provider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Providers.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
components: PropTypes.object.isRequired,
|
||||||
|
fields: PropTypes.object.isRequired,
|
||||||
|
getAdminInjectedComponents: PropTypes.func.isRequired,
|
||||||
|
getPlugin: PropTypes.func.isRequired,
|
||||||
|
localeNames: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||||
|
menu: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
to: PropTypes.string.isRequired,
|
||||||
|
icon: PropTypes.string,
|
||||||
|
intlLabel: PropTypes.shape({
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
defaultMessage: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
permissions: PropTypes.array,
|
||||||
|
Component: PropTypes.func,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
messages: PropTypes.object.isRequired,
|
||||||
|
plugins: PropTypes.object.isRequired,
|
||||||
|
runHookParallel: PropTypes.func.isRequired,
|
||||||
|
runHookWaterfall: PropTypes.func.isRequired,
|
||||||
|
runHookSeries: PropTypes.func.isRequired,
|
||||||
|
settings: PropTypes.object.isRequired,
|
||||||
|
store: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Providers;
|
||||||
20
packages/core/admin/admin/src/components/Theme/index.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ThemeProvider } from 'styled-components';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import GlobalStyle from '../GlobalStyle';
|
||||||
|
import Fonts from '../Fonts';
|
||||||
|
|
||||||
|
const Theme = ({ children, theme }) => (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<GlobalStyle />
|
||||||
|
<Fonts />
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
Theme.propTypes = {
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
theme: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Theme;
|
||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import LinkNotification from '../LinkNotification';
|
import LinkNotification from '../LinkNotification';
|
||||||
import basename from '../../../utils/basename';
|
import basename from '../../../core/utils/basename';
|
||||||
|
|
||||||
const MagicLink = ({ registrationToken }) => {
|
const MagicLink = ({ registrationToken }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 648 B After Width: | Height: | Size: 648 B |
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 244 B After Width: | Height: | Size: 244 B |
@ -0,0 +1,79 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { DateTime } from '@buffetjs/custom';
|
||||||
|
import { DatePicker, InputText, InputNumber, Select, TimePicker } from '@buffetjs/core';
|
||||||
|
import { DateWrapper } from './components';
|
||||||
|
|
||||||
|
function GenericInput({ type, onChange, value, ...rest }) {
|
||||||
|
switch (type) {
|
||||||
|
case 'boolean':
|
||||||
|
return <Select onChange={e => onChange(e.target.value)} value={value} {...rest} />;
|
||||||
|
|
||||||
|
case 'date':
|
||||||
|
case 'timestamp':
|
||||||
|
case 'timestampUpdate': {
|
||||||
|
const momentValue = moment(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DateWrapper type={type}>
|
||||||
|
<DatePicker
|
||||||
|
onChange={e => {
|
||||||
|
if (e.target.value) {
|
||||||
|
onChange(e.target.value.format('YYYY-MM-DD'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={momentValue}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</DateWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'datetime': {
|
||||||
|
const momentValue = moment(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DateWrapper type={type}>
|
||||||
|
<DateTime
|
||||||
|
onChange={e => {
|
||||||
|
if (e.target.value) {
|
||||||
|
onChange(e.target.value.toISOString());
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={momentValue}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</DateWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'enumeration':
|
||||||
|
return <Select onChange={e => onChange(e.target.value)} value={value} {...rest} />;
|
||||||
|
|
||||||
|
case 'integer':
|
||||||
|
case 'decimal':
|
||||||
|
case 'float':
|
||||||
|
return <InputNumber onChange={e => onChange(e.target.value)} value={value} {...rest} />;
|
||||||
|
|
||||||
|
case 'time':
|
||||||
|
return <TimePicker onChange={e => onChange(e.target.value)} value={value} {...rest} />;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "biginteger" type falls into this section
|
||||||
|
*/
|
||||||
|
default:
|
||||||
|
return <InputText onChange={e => onChange(e.target.value)} value={value} {...rest} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GenericInput.defaultProps = {
|
||||||
|
value: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
GenericInput.propTypes = {
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GenericInput;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { Button } from '@buffetjs/core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const StyledButton = styled(Button)`
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FormWrapper = styled.form`
|
||||||
|
min-width: 330px;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 13px 15px;
|
||||||
|
|
||||||
|
& > * + * {
|
||||||
|
margin-top: 11px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DateWrapper = styled.div`
|
||||||
|
display: ${({ type }) => (type === 'datetime' ? 'flex' : 'block')};
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import get from 'lodash/get';
|
||||||
|
import { useRBACProvider, findMatchingPermissions } from '@strapi/helper-plugin';
|
||||||
|
|
||||||
|
const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamiczone'];
|
||||||
|
|
||||||
|
const useAllowedAttributes = (contentType, slug) => {
|
||||||
|
const { allPermissions } = useRBACProvider();
|
||||||
|
|
||||||
|
let timestamps = get(contentType, ['options', 'timestamps']);
|
||||||
|
|
||||||
|
if (!Array.isArray(timestamps)) {
|
||||||
|
timestamps = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const readPermissionsForSlug = findMatchingPermissions(allPermissions, [
|
||||||
|
{
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: slug,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const readPermissionForAttr = get(readPermissionsForSlug, ['0', 'properties', 'fields'], []);
|
||||||
|
const attributesArray = Object.keys(get(contentType, ['attributes']), {});
|
||||||
|
const allowedAttributes = attributesArray
|
||||||
|
.filter(attr => {
|
||||||
|
const current = get(contentType, ['attributes', attr], {});
|
||||||
|
|
||||||
|
if (!current.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NOT_ALLOWED_FILTERS.includes(current.type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!readPermissionForAttr.includes(attr) && attr !== 'id' && !timestamps.includes(attr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
return allowedAttributes;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAllowedAttributes;
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Picker, Select } from '@buffetjs/core';
|
||||||
|
import {
|
||||||
|
FilterIcon,
|
||||||
|
getFilterType as comparatorsForType,
|
||||||
|
useTracking,
|
||||||
|
useQueryParams,
|
||||||
|
} from '@strapi/helper-plugin';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import useAllowedAttributes from './hooks/useAllowedAttributes';
|
||||||
|
import getTrad from '../../utils/getTrad';
|
||||||
|
import formatAttribute from './utils/formatAttribute';
|
||||||
|
import getAttributeType from './utils/getAttributeType';
|
||||||
|
import GenericInput from './GenericInput';
|
||||||
|
import { StyledButton, FormWrapper } from './components';
|
||||||
|
|
||||||
|
const AttributeFilter = ({ contentType, slug, metaData }) => {
|
||||||
|
const { trackUsage } = useTracking();
|
||||||
|
const [{ query }, setQuery] = useQueryParams();
|
||||||
|
|
||||||
|
const allowedAttributes = useAllowedAttributes(contentType, slug);
|
||||||
|
const [attribute, setAttribute] = useState(allowedAttributes[0]);
|
||||||
|
|
||||||
|
const attributeType = getAttributeType(attribute, contentType, metaData);
|
||||||
|
const comparators = comparatorsForType(attributeType);
|
||||||
|
const [comparator, setComparator] = useState(comparators[0].value);
|
||||||
|
|
||||||
|
const [value, setValue] = useState();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Picker
|
||||||
|
renderButtonContent={() => (
|
||||||
|
<>
|
||||||
|
<FilterIcon />
|
||||||
|
<FormattedMessage id="app.utils.filters" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
renderSectionContent={onToggle => {
|
||||||
|
const handleSubmit = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formattedAttribute = formatAttribute(attribute, metaData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When dealing with a "=" comparator, the filter should have a shape of {'attributeName': 'some value}
|
||||||
|
* otherwise, it should look like { 'attributeName_comparatorName' : 'some value' }
|
||||||
|
*/
|
||||||
|
const newFilter =
|
||||||
|
comparator === '='
|
||||||
|
? { [formattedAttribute]: value }
|
||||||
|
: { [`${formattedAttribute}${comparator}`]: value };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushing the filter in the URL for later refreshes or fast access
|
||||||
|
*/
|
||||||
|
const actualQuery = query || {};
|
||||||
|
const _where = actualQuery._where || [];
|
||||||
|
_where.push(newFilter);
|
||||||
|
setQuery({ ...actualQuery, _where, page: 1 });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracking stuff
|
||||||
|
*/
|
||||||
|
const useRelation = _where.some(obj => Object.keys(obj)[0].includes('.'));
|
||||||
|
trackUsage('didFilterEntries', { useRelation });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset to initial state
|
||||||
|
*/
|
||||||
|
setAttribute(allowedAttributes[0]);
|
||||||
|
setComparator(comparators[0].value);
|
||||||
|
setValue(undefined);
|
||||||
|
|
||||||
|
onToggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormWrapper onSubmit={handleSubmit}>
|
||||||
|
<Select
|
||||||
|
onChange={e => setAttribute(e.target.value)}
|
||||||
|
name="ct-filter"
|
||||||
|
options={allowedAttributes}
|
||||||
|
value={attribute}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
onChange={e => setComparator(e.target.value)}
|
||||||
|
name="comparator"
|
||||||
|
value={comparator}
|
||||||
|
options={comparators.map(comparator => (
|
||||||
|
<FormattedMessage id={comparator.id} key={comparator.value}>
|
||||||
|
{msg => <option value={comparator.value}>{msg}</option>}
|
||||||
|
</FormattedMessage>
|
||||||
|
))}
|
||||||
|
/>
|
||||||
|
<GenericInput name="input" onChange={setValue} type={attributeType} value={value} />
|
||||||
|
<StyledButton icon type="submit">
|
||||||
|
<FormattedMessage
|
||||||
|
id={getTrad('components.FiltersPickWrapper.PluginHeader.actions.apply')}
|
||||||
|
/>
|
||||||
|
</StyledButton>
|
||||||
|
</FormWrapper>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
AttributeFilter.propTypes = {
|
||||||
|
contentType: PropTypes.object.isRequired,
|
||||||
|
metaData: PropTypes.object.isRequired,
|
||||||
|
slug: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AttributeFilter;
|
||||||
@ -0,0 +1,388 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
import AttributeFilter from '..';
|
||||||
|
import addressCt from '../../../../../../../../admin-test-utils/lib/fixtures/collectionTypes/address';
|
||||||
|
import addressMetaData from '../../../../../../../../admin-test-utils/lib/fixtures/metaData/address';
|
||||||
|
|
||||||
|
class MockDate extends Date {
|
||||||
|
constructor() {
|
||||||
|
super(1992, 5, 21);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mock('react-intl', () => ({
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
FormattedMessage: ({ id }) => <option value={id}>{id}</option>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@strapi/helper-plugin', () => ({
|
||||||
|
...jest.requireActual('@strapi/helper-plugin'),
|
||||||
|
useRBACProvider: () => ({
|
||||||
|
allPermissions: [
|
||||||
|
{
|
||||||
|
id: 198,
|
||||||
|
action: 'plugins::content-manager.explorer.create',
|
||||||
|
subject: 'application::address.address',
|
||||||
|
properties: {
|
||||||
|
fields: [
|
||||||
|
'postal_coder',
|
||||||
|
'categories',
|
||||||
|
'cover',
|
||||||
|
'images',
|
||||||
|
'city',
|
||||||
|
'likes',
|
||||||
|
'json',
|
||||||
|
'slug',
|
||||||
|
'notrepeat_req.name',
|
||||||
|
'repeat_req.name',
|
||||||
|
'repeat_req_min.name',
|
||||||
|
],
|
||||||
|
locales: ['en'],
|
||||||
|
},
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 199,
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: 'application::address.address',
|
||||||
|
properties: {
|
||||||
|
fields: [
|
||||||
|
'postal_coder',
|
||||||
|
'categories',
|
||||||
|
'cover',
|
||||||
|
'images',
|
||||||
|
'city',
|
||||||
|
'likes',
|
||||||
|
'json',
|
||||||
|
'slug',
|
||||||
|
'notrepeat_req.name',
|
||||||
|
'repeat_req.name',
|
||||||
|
'repeat_req_min.name',
|
||||||
|
],
|
||||||
|
locales: ['en'],
|
||||||
|
},
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 200,
|
||||||
|
action: 'plugins::content-manager.explorer.update',
|
||||||
|
subject: 'application::address.address',
|
||||||
|
properties: {
|
||||||
|
fields: [
|
||||||
|
'postal_coder',
|
||||||
|
'categories',
|
||||||
|
'cover',
|
||||||
|
'images',
|
||||||
|
'city',
|
||||||
|
'likes',
|
||||||
|
'json',
|
||||||
|
'slug',
|
||||||
|
'notrepeat_req.name',
|
||||||
|
'repeat_req.name',
|
||||||
|
'repeat_req_min.name',
|
||||||
|
],
|
||||||
|
locales: ['en'],
|
||||||
|
},
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 258,
|
||||||
|
action: 'plugins::content-manager.explorer.delete',
|
||||||
|
subject: 'application::address.address',
|
||||||
|
properties: { locales: ['en'] },
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 269,
|
||||||
|
action: 'plugins::content-manager.explorer.publish',
|
||||||
|
subject: 'application::address.address',
|
||||||
|
properties: { locales: ['en'] },
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const renderComponent = () =>
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<AttributeFilter
|
||||||
|
contentType={addressCt}
|
||||||
|
metaData={addressMetaData}
|
||||||
|
slug="application::address.address"
|
||||||
|
/>
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('AttributeFilter', () => {
|
||||||
|
let realDate;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
realDate = global.Date;
|
||||||
|
global.Date = MockDate;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
global.Date = realDate;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('snapshots the filter dropdown with a set of valid fields', () => {
|
||||||
|
const { container } = renderComponent();
|
||||||
|
|
||||||
|
expect(container.querySelector('#ct-filter')).toMatchInlineSnapshot(`
|
||||||
|
.c0 {
|
||||||
|
width: 100%;
|
||||||
|
height: 3.4rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid #E3E9F3;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #333740;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding-right: 30px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjMiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNLjAxOCAwaDIwYTIgMiAwIDAgMSAyIDJ2MjhhMiAyIDAgMCAxLTIgMmgtMjBWMHoiIGZpbGw9IiNGQUZBRkIiLz48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNCM0I1QjkiPjxwYXRoIGQ9Ik0xNC4wMTggMTguMzc1YS4zNi4zNiAwIDAgMS0uMTEyLjI2NGwtMi42MjUgMi42MjVhLjM2LjM2IDAgMCAxLS4yNjMuMTExLjM2LjM2IDAgMCAxLS4yNjQtLjExMWwtMi42MjUtMi42MjVhLjM2LjM2IDAgMCAxLS4xMTEtLjI2NC4zNi4zNiAwIDAgMSAuMTExLS4yNjQuMzYuMzYgMCAwIDEgLjI2NC0uMTExaDUuMjVhLjM2LjM2IDAgMCAxIC4yNjMuMTExLjM2LjM2IDAgMCAxIC4xMTIuMjY0ek04LjAxOCAxNWEuMzYuMzYgMCAwIDEgLjExMS0uMjY0bDIuNjI1LTIuNjI1YS4zNi4zNiAwIDAgMSAuMjY0LS4xMTEuMzYuMzYgMCAwIDEgLjI2My4xMTFsMi42MjUgMi42MjVhLjM2LjM2IDAgMCAxIC4xMTIuMjY0LjM2LjM2IDAgMCAxLS4xMTIuMjY0LjM2LjM2IDAgMCAxLS4yNjMuMTExaC01LjI1YS4zNi4zNiAwIDAgMS0uMjY0LS4xMTEuMzYuMzYgMCAwIDEtLjExMS0uMjY0eiIvPjwvZz48L2c+PC9zdmc+Cg==);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0::-webkit-input-placeholder {
|
||||||
|
color: #919BAE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:focus {
|
||||||
|
border-color: #78caff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:disabled {
|
||||||
|
background-color: #FAFAFB;
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #9ea7b8;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
<select
|
||||||
|
autocomplete="off"
|
||||||
|
class="c0"
|
||||||
|
id="ct-filter"
|
||||||
|
name="ct-filter"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="categories"
|
||||||
|
>
|
||||||
|
categories
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="city"
|
||||||
|
>
|
||||||
|
city
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="created_at"
|
||||||
|
>
|
||||||
|
created_at
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="id"
|
||||||
|
>
|
||||||
|
id
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="likes"
|
||||||
|
>
|
||||||
|
likes
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="postal_coder"
|
||||||
|
>
|
||||||
|
postal_coder
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="slug"
|
||||||
|
>
|
||||||
|
slug
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="updated_at"
|
||||||
|
>
|
||||||
|
updated_at
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('snapshots the comparator dropdown with a set of valid comparator for the type', () => {
|
||||||
|
const { container } = renderComponent();
|
||||||
|
|
||||||
|
expect(container.querySelector('#comparator')).toMatchInlineSnapshot(`
|
||||||
|
.c0 {
|
||||||
|
width: 100%;
|
||||||
|
height: 3.4rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid #E3E9F3;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #333740;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding-right: 30px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjMiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNLjAxOCAwaDIwYTIgMiAwIDAgMSAyIDJ2MjhhMiAyIDAgMCAxLTIgMmgtMjBWMHoiIGZpbGw9IiNGQUZBRkIiLz48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iIGZpbGw9IiNCM0I1QjkiPjxwYXRoIGQ9Ik0xNC4wMTggMTguMzc1YS4zNi4zNiAwIDAgMS0uMTEyLjI2NGwtMi42MjUgMi42MjVhLjM2LjM2IDAgMCAxLS4yNjMuMTExLjM2LjM2IDAgMCAxLS4yNjQtLjExMWwtMi42MjUtMi42MjVhLjM2LjM2IDAgMCAxLS4xMTEtLjI2NC4zNi4zNiAwIDAgMSAuMTExLS4yNjQuMzYuMzYgMCAwIDEgLjI2NC0uMTExaDUuMjVhLjM2LjM2IDAgMCAxIC4yNjMuMTExLjM2LjM2IDAgMCAxIC4xMTIuMjY0ek04LjAxOCAxNWEuMzYuMzYgMCAwIDEgLjExMS0uMjY0bDIuNjI1LTIuNjI1YS4zNi4zNiAwIDAgMSAuMjY0LS4xMTEuMzYuMzYgMCAwIDEgLjI2My4xMTFsMi42MjUgMi42MjVhLjM2LjM2IDAgMCAxIC4xMTIuMjY0LjM2LjM2IDAgMCAxLS4xMTIuMjY0LjM2LjM2IDAgMCAxLS4yNjMuMTExaC01LjI1YS4zNi4zNiAwIDAgMS0uMjY0LS4xMTEuMzYuMzYgMCAwIDEtLjExMS0uMjY0eiIvPjwvZz48L2c+PC9zdmc+Cg==);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0::-webkit-input-placeholder {
|
||||||
|
color: #919BAE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:focus {
|
||||||
|
border-color: #78caff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:disabled {
|
||||||
|
background-color: #FAFAFB;
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #9ea7b8;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
<select
|
||||||
|
autocomplete="off"
|
||||||
|
class="c0"
|
||||||
|
id="comparator"
|
||||||
|
name="comparator"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES.="
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES.=
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES._ne"
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES._ne
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES._lt"
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES._lt
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES._lte"
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES._lte
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES._gt"
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES._gt
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES._gte"
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES._gte
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES._contains"
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES._contains
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="components.FilterOptions.FILTER_TYPES._containss"
|
||||||
|
>
|
||||||
|
components.FilterOptions.FILTER_TYPES._containss
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes the input component when selecting an attribute with a different type', () => {
|
||||||
|
const { container } = renderComponent();
|
||||||
|
|
||||||
|
fireEvent.change(container.querySelector('#ct-filter'), { target: { value: 'updated_at' } });
|
||||||
|
|
||||||
|
expect(container.querySelector('#date')).toMatchInlineSnapshot(`
|
||||||
|
.c0 {
|
||||||
|
width: 100%;
|
||||||
|
height: 3.4rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
cursor: text;
|
||||||
|
outline: 0;
|
||||||
|
border: 1px solid #E3E9F3;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #333740;
|
||||||
|
background-color: transparent;
|
||||||
|
padding-left: calc(3.4rem + 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0::-webkit-input-placeholder {
|
||||||
|
color: #919BAE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:focus {
|
||||||
|
border-color: #78caff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c0:disabled {
|
||||||
|
background-color: #FAFAFB;
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: #9ea7b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
<input
|
||||||
|
autocomplete="off"
|
||||||
|
class="c0"
|
||||||
|
id="date"
|
||||||
|
name="start_date"
|
||||||
|
tabindex="0"
|
||||||
|
type="text"
|
||||||
|
value="June 21, 1992"
|
||||||
|
/>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pushes the query in the URl when validating the filter form using the "equal comparator"', () => {
|
||||||
|
const { container } = renderComponent();
|
||||||
|
|
||||||
|
fireEvent.change(container.querySelector('#input'), { target: { value: 'hello world' } });
|
||||||
|
fireEvent.click(container.querySelector('[type="submit"]'));
|
||||||
|
|
||||||
|
expect(window.location.href).toBe(
|
||||||
|
'http://localhost:4000/admin?_where[0][categories.name]=hello%20world&page=1'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pushes the query in the URl when validating the filter form using the "not equal comparator"', () => {
|
||||||
|
const { container } = renderComponent();
|
||||||
|
|
||||||
|
fireEvent.change(container.querySelector('#comparator'), {
|
||||||
|
target: { value: 'components.FilterOptions.FILTER_TYPES._ne' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.change(container.querySelector('#input'), { target: { value: 'hello world' } });
|
||||||
|
fireEvent.click(container.querySelector('[type="submit"]'));
|
||||||
|
|
||||||
|
expect(window.location.href).toBe(
|
||||||
|
'http://localhost:4000/admin?_where[0][categories.name]=hello%20world&_where[1][categories.namecomponents.FilterOptions.FILTER_TYPES._ne]=hello%20world&page=1'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import get from 'lodash/get';
|
||||||
|
|
||||||
|
const formatAttribute = (attributeName, metaData) => {
|
||||||
|
const mainField = get(metaData, [attributeName, 'list', 'mainField', 'name']);
|
||||||
|
|
||||||
|
if (mainField) {
|
||||||
|
return `${attributeName}.${mainField}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default formatAttribute;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
import get from 'lodash/get';
|
||||||
|
|
||||||
|
const getAttributeType = (attributeName, contentType, metaData) => {
|
||||||
|
let attributeType = get(contentType, ['attributes', attributeName, 'type'], '');
|
||||||
|
|
||||||
|
if (attributeType === 'relation') {
|
||||||
|
attributeType = get(metaData, [attributeName, 'list', 'mainField', 'schema', 'type'], 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributeType === 'string' ? 'text' : attributeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAttributeType;
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { get } from 'lodash';
|
import axios from 'axios';
|
||||||
|
import get from 'lodash/get';
|
||||||
import {
|
import {
|
||||||
request,
|
|
||||||
useTracking,
|
useTracking,
|
||||||
useNotification,
|
useNotification,
|
||||||
useQueryParams,
|
useQueryParams,
|
||||||
@ -12,8 +12,13 @@ import {
|
|||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { createDefaultForm, getTrad, removePasswordFieldsFromData } from '../../utils';
|
import { axiosInstance } from '../../../core/utils';
|
||||||
import pluginId from '../../pluginId';
|
import {
|
||||||
|
createDefaultForm,
|
||||||
|
getTrad,
|
||||||
|
getRequestUrl,
|
||||||
|
removePasswordFieldsFromData,
|
||||||
|
} from '../../utils';
|
||||||
import { useFindRedirectionLink } from '../../hooks';
|
import { useFindRedirectionLink } from '../../hooks';
|
||||||
import {
|
import {
|
||||||
getData,
|
getData,
|
||||||
@ -25,7 +30,6 @@ import {
|
|||||||
submitSucceeded,
|
submitSucceeded,
|
||||||
} from '../../sharedReducers/crudReducer/actions';
|
} from '../../sharedReducers/crudReducer/actions';
|
||||||
import selectCrudReducer from '../../sharedReducers/crudReducer/selectors';
|
import selectCrudReducer from '../../sharedReducers/crudReducer/selectors';
|
||||||
import { getRequestUrl } from './utils';
|
|
||||||
|
|
||||||
// This container is used to handle the CRUD
|
// This container is used to handle the CRUD
|
||||||
const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }) => {
|
const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }) => {
|
||||||
@ -55,7 +59,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getRequestUrl(`${slug}/${origin || id}`);
|
return getRequestUrl(`collection-types/${slug}/${origin || id}`);
|
||||||
}, [slug, id, isCreatingEntry, origin]);
|
}, [slug, id, isCreatingEntry, origin]);
|
||||||
|
|
||||||
const cleanClonedData = useCallback(
|
const cleanClonedData = useCallback(
|
||||||
@ -127,18 +131,18 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const abortController = new AbortController();
|
const CancelToken = axios.CancelToken;
|
||||||
const { signal } = abortController;
|
const source = CancelToken.source();
|
||||||
|
|
||||||
const fetchData = async signal => {
|
const fetchData = async source => {
|
||||||
dispatch(getData());
|
dispatch(getData());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await request(requestURL, { method: 'GET', signal });
|
const { data } = await axiosInstance.get(requestURL, { cancelToken: source.token });
|
||||||
|
|
||||||
dispatch(getDataSucceeded(cleanReceivedData(cleanClonedData(data))));
|
dispatch(getDataSucceeded(cleanReceivedData(cleanClonedData(data))));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.name === 'AbortError') {
|
if (axios.isCancel(err)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,13 +179,13 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (requestURL) {
|
if (requestURL) {
|
||||||
fetchData(signal);
|
fetchData(source);
|
||||||
} else {
|
} else {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
abortController.abort();
|
source.cancel('Operation canceled by the user.');
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
cleanClonedData,
|
cleanClonedData,
|
||||||
@ -218,9 +222,9 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
try {
|
try {
|
||||||
trackUsageRef.current('willDeleteEntry', trackerProperty);
|
trackUsageRef.current('willDeleteEntry', trackerProperty);
|
||||||
|
|
||||||
const response = await request(getRequestUrl(`${slug}/${id}`), {
|
const { data } = await axiosInstance.delete(
|
||||||
method: 'DELETE',
|
getRequestUrl(`collection-types/${slug}/${id}`)
|
||||||
});
|
);
|
||||||
|
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@ -229,7 +233,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
|
|
||||||
trackUsageRef.current('didDeleteEntry', trackerProperty);
|
trackUsageRef.current('didDeleteEntry', trackerProperty);
|
||||||
|
|
||||||
return Promise.resolve(response);
|
return Promise.resolve(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
trackUsageRef.current('didNotDeleteEntry', { error: err, ...trackerProperty });
|
trackUsageRef.current('didNotDeleteEntry', { error: err, ...trackerProperty });
|
||||||
|
|
||||||
@ -245,13 +249,13 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
|
|
||||||
const onPost = useCallback(
|
const onPost = useCallback(
|
||||||
async (body, trackerProperty) => {
|
async (body, trackerProperty) => {
|
||||||
const endPoint = `${getRequestUrl(slug)}${rawQuery}`;
|
const endPoint = `${getRequestUrl(`collection-types/${slug}`)}${rawQuery}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Show a loading button in the EditView/Header.js && lock the app => no navigation
|
// Show a loading button in the EditView/Header.js && lock the app => no navigation
|
||||||
dispatch(setStatus('submit-pending'));
|
dispatch(setStatus('submit-pending'));
|
||||||
|
|
||||||
const response = await request(endPoint, { method: 'POST', body });
|
const { data } = await axiosInstance.post(endPoint, body);
|
||||||
|
|
||||||
trackUsageRef.current('didCreateEntry', trackerProperty);
|
trackUsageRef.current('didCreateEntry', trackerProperty);
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
@ -259,11 +263,11 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
message: { id: getTrad('success.record.save') },
|
message: { id: getTrad('success.record.save') },
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(submitSucceeded(cleanReceivedData(response)));
|
dispatch(submitSucceeded(cleanReceivedData(data)));
|
||||||
// Enable navigation and remove loaders
|
// Enable navigation and remove loaders
|
||||||
dispatch(setStatus('resolved'));
|
dispatch(setStatus('resolved'));
|
||||||
|
|
||||||
replace(`/plugins/${pluginId}/collectionType/${slug}/${response.id}${rawQuery}`);
|
replace(`/content-manager/collectionType/${slug}/${data.id}${rawQuery}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
trackUsageRef.current('didNotCreateEntry', { error: err, trackerProperty });
|
trackUsageRef.current('didNotCreateEntry', { error: err, trackerProperty });
|
||||||
displayErrors(err);
|
displayErrors(err);
|
||||||
@ -276,11 +280,11 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
const onPublish = useCallback(async () => {
|
const onPublish = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
trackUsageRef.current('willPublishEntry');
|
trackUsageRef.current('willPublishEntry');
|
||||||
const endPoint = getRequestUrl(`${slug}/${id}/actions/publish`);
|
const endPoint = getRequestUrl(`collection-types/${slug}/${id}/actions/publish`);
|
||||||
|
|
||||||
dispatch(setStatus('publish-pending'));
|
dispatch(setStatus('publish-pending'));
|
||||||
|
|
||||||
const data = await request(endPoint, { method: 'POST' });
|
const { data } = await axiosInstance.post(endPoint);
|
||||||
|
|
||||||
trackUsageRef.current('didPublishEntry');
|
trackUsageRef.current('didPublishEntry');
|
||||||
|
|
||||||
@ -299,14 +303,14 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
|
|
||||||
const onPut = useCallback(
|
const onPut = useCallback(
|
||||||
async (body, trackerProperty) => {
|
async (body, trackerProperty) => {
|
||||||
const endPoint = getRequestUrl(`${slug}/${id}`);
|
const endPoint = getRequestUrl(`collection-types/${slug}/${id}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trackUsageRef.current('willEditEntry', trackerProperty);
|
trackUsageRef.current('willEditEntry', trackerProperty);
|
||||||
|
|
||||||
dispatch(setStatus('submit-pending'));
|
dispatch(setStatus('submit-pending'));
|
||||||
|
|
||||||
const response = await request(endPoint, { method: 'PUT', body });
|
const { data } = await axiosInstance.put(endPoint, body);
|
||||||
|
|
||||||
trackUsageRef.current('didEditEntry', { trackerProperty });
|
trackUsageRef.current('didEditEntry', { trackerProperty });
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
@ -314,7 +318,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
message: { id: getTrad('success.record.save') },
|
message: { id: getTrad('success.record.save') },
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(submitSucceeded(cleanReceivedData(response)));
|
dispatch(submitSucceeded(cleanReceivedData(data)));
|
||||||
|
|
||||||
dispatch(setStatus('resolved'));
|
dispatch(setStatus('resolved'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -328,14 +332,14 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onUnpublish = useCallback(async () => {
|
const onUnpublish = useCallback(async () => {
|
||||||
const endPoint = getRequestUrl(`${slug}/${id}/actions/unpublish`);
|
const endPoint = getRequestUrl(`collection-types/${slug}/${id}/actions/unpublish`);
|
||||||
|
|
||||||
dispatch(setStatus('unpublish-pending'));
|
dispatch(setStatus('unpublish-pending'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
trackUsageRef.current('willUnpublishEntry');
|
trackUsageRef.current('willUnpublishEntry');
|
||||||
|
|
||||||
const response = await request(endPoint, { method: 'POST' });
|
const { data } = await axiosInstance.post(endPoint);
|
||||||
|
|
||||||
trackUsageRef.current('didUnpublishEntry');
|
trackUsageRef.current('didUnpublishEntry');
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
@ -343,7 +347,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
|
|||||||
message: { id: getTrad('success.record.unpublish') },
|
message: { id: getTrad('success.record.unpublish') },
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(submitSucceeded(cleanReceivedData(response)));
|
dispatch(submitSucceeded(cleanReceivedData(data)));
|
||||||
dispatch(setStatus('resolved'));
|
dispatch(setStatus('resolved'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
dispatch(setStatus('resolved'));
|
dispatch(setStatus('resolved'));
|
||||||
@ -2,8 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useContentManagerEditViewDataManager } from '@strapi/helper-plugin';
|
import { useContentManagerEditViewDataManager } from '@strapi/helper-plugin';
|
||||||
import pluginId from '../../pluginId';
|
import { getTrad } from '../../utils';
|
||||||
|
|
||||||
import NonRepeatableWrapper from '../NonRepeatableWrapper';
|
import NonRepeatableWrapper from '../NonRepeatableWrapper';
|
||||||
import PlusButton from '../PlusButton';
|
import PlusButton from '../PlusButton';
|
||||||
import P from './P';
|
import P from './P';
|
||||||
@ -22,7 +21,7 @@ const ComponentInitializer = ({ componentUid, isReadOnly, name }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlusButton type="button" />
|
<PlusButton type="button" />
|
||||||
<FormattedMessage id={`${pluginId}.components.empty-repeatable`}>
|
<FormattedMessage id={getTrad('components.empty-repeatable')}>
|
||||||
{msg => <P style={{ paddingTop: 78 }}>{msg}</P>}
|
{msg => <P style={{ paddingTop: 78 }}>{msg}</P>}
|
||||||
</FormattedMessage>
|
</FormattedMessage>
|
||||||
</NonRepeatableWrapper>
|
</NonRepeatableWrapper>
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import pluginId from '../../../pluginId';
|
import { getTrad } from '../../../utils';
|
||||||
import useListView from '../../../hooks/useListView';
|
import useListView from '../../../hooks/useListView';
|
||||||
import DeleteAll from './DeleteAll';
|
import DeleteAll from './DeleteAll';
|
||||||
import Delete from './Delete';
|
import Delete from './Delete';
|
||||||
@ -18,12 +18,12 @@ function ActionCollapse({ colSpan }) {
|
|||||||
<Wrapper colSpan={colSpan}>
|
<Wrapper colSpan={colSpan}>
|
||||||
<td colSpan={colSpan}>
|
<td colSpan={colSpan}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={`${pluginId}.components.TableDelete.entries.${suffix}`}
|
id={getTrad(`components.TableDelete.entries.${suffix}`)}
|
||||||
values={{ number }}
|
values={{ number }}
|
||||||
>
|
>
|
||||||
{message => <Delete>{message}</Delete>}
|
{message => <Delete>{message}</Delete>}
|
||||||
</FormattedMessage>
|
</FormattedMessage>
|
||||||
<FormattedMessage id={`${pluginId}.components.TableDelete.${deleteMessageId}`}>
|
<FormattedMessage id={getTrad(`components.TableDelete.${deleteMessageId}`)}>
|
||||||
{message => <DeleteAll onClick={toggleModalDeleteAll}>{message}</DeleteAll>}
|
{message => <DeleteAll onClick={toggleModalDeleteAll}>{message}</DeleteAll>}
|
||||||
</FormattedMessage>
|
</FormattedMessage>
|
||||||
</td>
|
</td>
|
||||||
@ -5,8 +5,8 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Grab, GrabLarge, Pencil } from '@buffetjs/icons';
|
import { Grab, GrabLarge, Pencil } from '@buffetjs/icons';
|
||||||
import { CheckPermissions } from '@strapi/helper-plugin';
|
import { CheckPermissions } from '@strapi/helper-plugin';
|
||||||
import pluginId from '../../pluginId';
|
import { getTrad } from '../../utils';
|
||||||
import pluginPermissions from '../../permissions';
|
import permissions from '../../../permissions';
|
||||||
import useLayoutDnd from '../../hooks/useLayoutDnd';
|
import useLayoutDnd from '../../hooks/useLayoutDnd';
|
||||||
import GrabWrapper from './GrabWrapper';
|
import GrabWrapper from './GrabWrapper';
|
||||||
import Link from './Link';
|
import Link from './Link';
|
||||||
@ -16,6 +16,8 @@ import SubWrapper from './SubWrapper';
|
|||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
import Close from './Close';
|
import Close from './Close';
|
||||||
|
|
||||||
|
const cmPermissions = permissions.contentManager;
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const DraggedField = forwardRef(
|
const DraggedField = forwardRef(
|
||||||
(
|
(
|
||||||
@ -132,14 +134,14 @@ const DraggedField = forwardRef(
|
|||||||
</SubWrapper>
|
</SubWrapper>
|
||||||
)}
|
)}
|
||||||
{type === 'component' && (
|
{type === 'component' && (
|
||||||
<CheckPermissions permissions={pluginPermissions.componentsConfigurations}>
|
<CheckPermissions permissions={cmPermissions.componentsConfigurations}>
|
||||||
<FormattedMessage id={`${pluginId}.components.FieldItem.linkToComponentLayout`}>
|
<FormattedMessage id={getTrad('components.FieldItem.linkToComponentLayout')}>
|
||||||
{msg => (
|
{msg => (
|
||||||
<Link
|
<Link
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
goTo(`/plugins/${pluginId}/components/${componentUid}/configurations/edit`);
|
goTo(`/content-manager/components/${componentUid}/configurations/edit`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon="cog" />
|
<FontAwesomeIcon icon="cog" />
|
||||||
@ -2,11 +2,12 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { hasPermissions, useRBACProvider } from '@strapi/helper-plugin';
|
import { hasPermissions, useRBACProvider } from '@strapi/helper-plugin';
|
||||||
import pluginId from '../../pluginId';
|
import permissions from '../../../permissions';
|
||||||
import pluginPermissions from '../../permissions';
|
|
||||||
import DynamicComponentCard from '../DynamicComponentCard';
|
import DynamicComponentCard from '../DynamicComponentCard';
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip from './Tooltip';
|
||||||
|
|
||||||
|
const cmPermissions = permissions.contentManager;
|
||||||
|
|
||||||
const DynamicComponent = ({ componentUid, friendlyName, icon, setIsOverDynamicZone }) => {
|
const DynamicComponent = ({ componentUid, friendlyName, icon, setIsOverDynamicZone }) => {
|
||||||
const [isOver, setIsOver] = useState(false);
|
const [isOver, setIsOver] = useState(false);
|
||||||
const [{ isLoading, canAccess }, setState] = useState({ isLoading: true, canAccess: false });
|
const [{ isLoading, canAccess }, setState] = useState({ isLoading: true, canAccess: false });
|
||||||
@ -18,7 +19,7 @@ const DynamicComponent = ({ componentUid, friendlyName, icon, setIsOverDynamicZo
|
|||||||
try {
|
try {
|
||||||
const canAccess = await hasPermissions(
|
const canAccess = await hasPermissions(
|
||||||
allPermissions,
|
allPermissions,
|
||||||
pluginPermissions.componentsConfigurations
|
cmPermissions.componentsConfigurations
|
||||||
);
|
);
|
||||||
|
|
||||||
setState({ isLoading: false, canAccess });
|
setState({ isLoading: false, canAccess });
|
||||||
@ -44,7 +45,7 @@ const DynamicComponent = ({ componentUid, friendlyName, icon, setIsOverDynamicZo
|
|||||||
isOver={isOver}
|
isOver={isOver}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isLoading && canAccess) {
|
if (!isLoading && canAccess) {
|
||||||
push(`/plugins/${pluginId}/components/${componentUid}/configurations/edit`);
|
push(`/content-manager/components/${componentUid}/configurations/edit`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onMouseEvent={handleMouseEvent}
|
onMouseEvent={handleMouseEvent}
|
||||||
@ -3,7 +3,7 @@ import { groupBy } from 'lodash';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Collapse } from 'reactstrap';
|
import { Collapse } from 'reactstrap';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import pluginId from '../../../pluginId';
|
import { getTrad } from '../../../utils';
|
||||||
import { useContentTypeLayout } from '../../../hooks';
|
import { useContentTypeLayout } from '../../../hooks';
|
||||||
import Category from './Category';
|
import Category from './Category';
|
||||||
import Wrapper from './Wrapper';
|
import Wrapper from './Wrapper';
|
||||||
@ -56,7 +56,7 @@ const Picker = ({ components, isOpen, onClickAddComponent }) => {
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
<div>
|
<div>
|
||||||
<p className="componentPickerTitle">
|
<p className="componentPickerTitle">
|
||||||
<FormattedMessage id={`${pluginId}.components.DynamicZone.pick-compo`} />
|
<FormattedMessage id={getTrad('components.DynamicZone.pick-compo')} />
|
||||||
</p>
|
</p>
|
||||||
<div className="categoriesList">
|
<div className="categoriesList">
|
||||||
{dynamicComponentCategories.map(({ category, components }, index) => {
|
{dynamicComponentCategories.map(({ category, components }, index) => {
|
||||||
@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { Flex } from '@buffetjs/core';
|
import { Flex } from '@buffetjs/core';
|
||||||
import { LabelIconWrapper, NotAllowedInput, useNotification } from '@strapi/helper-plugin';
|
import { LabelIconWrapper, NotAllowedInput, useNotification } from '@strapi/helper-plugin';
|
||||||
import pluginId from '../../pluginId';
|
import { getTrad } from '../../utils';
|
||||||
import connect from './utils/connect';
|
import connect from './utils/connect';
|
||||||
import select from './utils/select';
|
import select from './utils/select';
|
||||||
import BaselineAlignement from './BaselineAlignement';
|
import BaselineAlignement from './BaselineAlignement';
|
||||||
@ -74,7 +74,7 @@ const DynamicZone = ({
|
|||||||
} else {
|
} else {
|
||||||
toggleNotification({
|
toggleNotification({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
message: { id: `${pluginId}.components.notification.info.maximum-requirement` },
|
message: { id: getTrad('components.notification.info.maximum-requirement') },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -160,7 +160,7 @@ const DynamicZone = ({
|
|||||||
/>
|
/>
|
||||||
{hasRequiredError && !isOpen && !hasMaxError && (
|
{hasRequiredError && !isOpen && !hasMaxError && (
|
||||||
<div className="error-label">
|
<div className="error-label">
|
||||||
<FormattedMessage id={`${pluginId}.components.DynamicZone.required`} />
|
<FormattedMessage id={getTrad('components.DynamicZone.required')} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasMaxError && !isOpen && (
|
{hasMaxError && !isOpen && (
|
||||||
@ -171,16 +171,18 @@ const DynamicZone = ({
|
|||||||
{hasMinError && !isOpen && (
|
{hasMinError && !isOpen && (
|
||||||
<div className="error-label">
|
<div className="error-label">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={`${pluginId}.components.DynamicZone.missing${
|
id={getTrad(
|
||||||
missingComponentNumber > 1 ? '.plural' : '.singular'
|
`components.DynamicZone.missing${
|
||||||
}`}
|
missingComponentNumber > 1 ? '.plural' : '.singular'
|
||||||
|
}`
|
||||||
|
)}
|
||||||
values={{ count: missingComponentNumber }}
|
values={{ count: missingComponentNumber }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={`${pluginId}.components.DynamicZone.add-compo`}
|
id={getTrad('components.DynamicZone.add-compo')}
|
||||||
values={{ componentName: metadatas.label }}
|
values={{ componentName: metadatas.label }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -6,7 +6,7 @@ import { FormattedMessage, useIntl } from 'react-intl';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import { NotAllowedInput, LabelIconWrapper } from '@strapi/helper-plugin';
|
import { NotAllowedInput, LabelIconWrapper } from '@strapi/helper-plugin';
|
||||||
import pluginId from '../../pluginId';
|
import { getTrad } from '../../utils';
|
||||||
import ComponentInitializer from '../ComponentInitializer';
|
import ComponentInitializer from '../ComponentInitializer';
|
||||||
import NonRepeatableComponent from '../NonRepeatableComponent';
|
import NonRepeatableComponent from '../NonRepeatableComponent';
|
||||||
import RepeatableComponent from '../RepeatableComponent';
|
import RepeatableComponent from '../RepeatableComponent';
|
||||||
@ -94,7 +94,7 @@ const FieldComponent = ({
|
|||||||
removeComponentFromField(name, componentUid);
|
removeComponentFromField(name, componentUid);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage id={`${pluginId}.components.reset-entry`} />
|
<FormattedMessage id={getTrad('components.reset-entry')} />
|
||||||
<div />
|
<div />
|
||||||
</Reset>
|
</Reset>
|
||||||
)}
|
)}
|
||||||