Merge branch 'features/webhooks' of github.com:strapi/strapi into front/webhooks-editview

This commit is contained in:
Virginie Ky 2020-01-09 11:11:32 +01:00
commit 4aeb5399ee
78 changed files with 1377 additions and 428 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -134,4 +134,4 @@ Check out our [roadmap](https://portal.productboard.com/strapi) to get informed
## License
[MIT License](LICENSE.md) Copyright (c) 2015-2019 [Strapi Solutions](https://strapi.io/).
[MIT License](LICENSE.md) Copyright (c) 2015-2020 [Strapi Solutions](https://strapi.io/).

View File

@ -14,9 +14,9 @@ You will also need another `string` field that contains the slugified value of y
Let's configure the layout of the **edit page** to make it more user friendly for the content editor.
- Click on the **Content Manager** link in the left menu.
- Then on the `Article` Content Type.
- And finally on the **Edit View** tab.
- Click on the **Article** link in the left menu.
- Then on the `+ Add New Article` button.
- And finally on the `Configure the view` button.
Here we will be able to setup the `slug` field.

View File

@ -91,6 +91,23 @@ query {
}
```
### Fetch dynamic zone data
Dynamic zones are union types in graphql so you need to use fragments to query the fields.
```
query {
restaurants {
dz {
__typename
... on ComponentDefaultClosingperiod {
label
}
}
}
}
```
### Create a new entry
- `input`: Object

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -19,7 +19,7 @@
"app.components.BlockLink.code": "Příklady kódu",
"app.components.BlockLink.code.content": "Naučte se na příkladech vytvořených komunitou.",
"app.components.BlockLink.documentation": "Přečíst dokumentaci",
"app.components.BlockLink.documentation.content": "Objevte koncepty, referenční průvodce a tutoriály.",
"app.components.BlockLink.documentation.content": "Objevte koncepty, referenční průvodce a návody.",
"app.components.Button.cancel": "Zrušit",
"app.components.Button.save": "Uložit",
"app.components.ComingSoonPage.comingSoon": "Již brzy",
@ -34,7 +34,7 @@
"app.components.HomePage.create": "Vytvořte svůj první Typ obsahu",
"app.components.HomePage.createBlock.content.first": "Tento ",
"app.components.HomePage.createBlock.content.second": " zásuvný modul vám pomůže definovat datovou strukturu vašich modelů. Pokud jste zde nový, velmi doporučujeme postupovat dle našeho ",
"app.components.HomePage.createBlock.content.tutorial": " tutoriálu.",
"app.components.HomePage.createBlock.content.tutorial": " návodu.",
"app.components.HomePage.cta": "POTVRDIT",
"app.components.HomePage.newsLetter": "Přihlaste se k odběru newsletteru a zůstaňte v kontaktu s projektem Strapi",
"app.components.HomePage.support": "PODPOŘTE NÁS",
@ -105,6 +105,9 @@
"app.components.listPlugins.title.plural": "{number} zásuvných modulů je instalováno",
"app.components.listPlugins.title.singular": "{number} zásuvný modul je nainstalován",
"app.components.listPluginsPage.deletePlugin.error": "Došlo k chybě při odinstalaci zásuvného modulu",
"app.links.configure-view": "Upravit vzhled",
"app.utils.SelectOption.defaultMessage": " ",
"app.utils.defaultMessage": " ",
"app.utils.placeholder.defaultMessage": " ",
@ -112,7 +115,7 @@
"components.AutoReloadBlocker.header": "Pro tento zásuvný modul musí být zapnuta funkce znovu načítání.",
"components.ErrorBoundary.title": "Něco se pokazilo...",
"components.Input.error.attribute.key.taken": "Tato hodnota již existuje",
"components.Input.error.attribute.sameKeyAndName": "Nemůže se rovnat",
"components.Input.error.attribute.sameKeyAndName": "Hodnoty nesmí být stejné",
"components.Input.error.attribute.taken": "Název tohoto pole již existuje",
"components.Input.error.contentTypeName.taken": "Tento název již existuje",
"components.Input.error.custom-error": "{errorMessage} ",
@ -123,9 +126,11 @@
"components.Input.error.validation.min": "Hodnota je příliš nízká.",
"components.Input.error.validation.minLength": "Hodnota je příliš krátká.",
"components.Input.error.validation.minSupMax": "Nemůže být nadřazený",
"components.Input.error.validation.regex": "Hodnota neodpovídá regexu.",
"components.Input.error.validation.regex": "Hodnota neodpovídá požadovanému vzoru.",
"components.Input.error.validation.required": "Tato hodnota je povinná.",
"components.Input.error.validation.unique": "Tato hodnota již byla použita.",
"components.InputSelect.option.placeholder": "Vyberte zde",
"component.Input.error.validation.integer": "Tato hodnota musí být celé číslo",
"components.ListRow.empty": "Žádná data k zobrazení.",
"components.OverlayBlocker.description": "Používáte funkcionalitu, která potřebuje restartovat server. Počkejte, až se server opět spustí.",
"components.OverlayBlocker.description.serverError": "Server by měl být restartován, zkontrolujte prosím protokoly v terminálu.",

View File

@ -97,7 +97,7 @@
"app.components.PluginCard.compatible": "Compatible with your app",
"app.components.PluginCard.compatibleCommunity": "Compatible with the community",
"app.components.PluginCard.more-details": "More details",
"app.components.PluginCard.PopUpWarning.install.impossible.autoReload.needed": "The autoReload feature needs to be unabled. Please start you app with `yarn develop`.",
"app.components.PluginCard.PopUpWarning.install.impossible.autoReload.needed": "The autoReload feature needs to be enabled. Please start your app with `yarn develop`.",
"app.components.PluginCard.PopUpWarning.install.impossible.environment": "For security reasons, a plugin can only be downloaded in a development environment.",
"app.components.PluginCard.PopUpWarning.install.impossible.confirm": "I understand!",
"app.components.PluginCard.PopUpWarning.install.impossible.title": "Downloading is impossible",

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -5,6 +5,7 @@ const fse = require('fs-extra');
const chalk = require('chalk');
const execa = require('execa');
const ora = require('ora');
const _ = require('lodash');
const stopProcess = require('./utils/stop-process');
const { trackUsage, captureStderr } = require('./utils/usage');
@ -42,7 +43,7 @@ module.exports = async function createProject(
strapiDependencies: scope.strapiDependencies,
additionalsDependencies: dependencies,
strapiVersion: scope.strapiVersion,
projectName: scope.name,
projectName: _.kebabCase(scope.name),
uuid: scope.uuid,
}),
{

View File

@ -0,0 +1,3 @@
{
"welcome": "Vítejte"
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,15 +1,19 @@
{
"models": "Druhy obsahu",
"models.numbered": "Druhy obsahu ({number})",
"models": "Typy obsahu",
"models.numbered": "Typy obsahu ({number})",
"groups": "Skupiny",
"groups.numbered": "Skupiny ({number})",
"EditRelations.title": "Relační data",
"components.AddFilterCTA.add": "Filtry",
"components.AddFilterCTA.hide": "Filtry",
"components.DraggableAttr.edit": "Klikněte pro úpravu",
"components.DraggableAttr.edit": "Upravte kliknutím",
"components.DynamicZone.add-compo": "Přidat do {componentName}",
"components.DynamicZone.pick-compo": "Vyberte jeden komponent",
"components.DynamicZone.missing.singular": "Chybějící komponenty: {count}",
"components.DynamicZone.missing.plural": "Chybějící komponenty: {count}",
"components.EmptyAttributesBlock.button": "Přejít k nastavení",
"components.EmptyAttributesBlock.description": "Můžete upravit svá nastavení",
"components.FieldItem.linkToGroupLayout": "Nastavit rozložení skupiny",
"components.FieldItem.linkToComponentLayout": "Nastavit rozložení komponentu",
"components.FilterOptions.FILTER_TYPES.=": "je",
"components.FilterOptions.FILTER_TYPES._contains": "obsahuje",
"components.FilterOptions.FILTER_TYPES._containss": "obsahuje (citlivé na velká písmena)",
@ -26,12 +30,17 @@
"components.FiltersPickWrapper.PluginHeader.description": "Nastavit pravidla aplikování filtrů na záznamy",
"components.FiltersPickWrapper.PluginHeader.title.filter": "Filtry",
"components.FiltersPickWrapper.hide": "Skrýt",
"components.Group.notification.info.minimum-requirement": "Pole bylo přidáno do vaší skupiny aby byly splněna minimální kritéria validace.",
"components.Group.notification.info.maximum-requirement": "Již jste dosáhli maximálního množství polí",
"components.Group.empty.repeatable": "Zatím žádný záznam. Klikněte na tlačítko níže k jeho přidání.",
"components.Group.reset": "Resetovat záznam",
"components.notification.info.minimum-requirement": "Pole bylo přidáno pro splnění minimálních požadavků",
"components.notification.info.maximum-requirement": "Již jste dosáhli maximálního počtu polí",
"components.empty-repeatable": "Zatím zde není žádný záznam. Kliknutím na tlačítko níže jej přidáte.",
"components.reset-entry": "Zrušit záznam",
"components.LimitSelect.itemsPerPage": "Položek na stránku",
"components.Search.placeholder": "Vyhledat záznam...",
"components.SettingsViewWrapper.pluginHeader.title": "Nastavit zobrazení - {name}",
"components.SettingsViewWrapper.pluginHeader.description.edit-settings": "Upravit vzhled zobrazení úprav.",
"components.SettingsViewWrapper.pluginHeader.description.list-settings": "Zvolte nastavení zobrazení záznamu.",
"components.TableDelete.delete": "Odstranit vše",
"components.TableDelete.deleteSelected": "Odstranit výběr",
"components.TableDelete.entries.plural": "{number} položek vybráno",
@ -51,44 +60,53 @@
"containers.Edit.submit": "Uložit",
"containers.Edit.Link.Layout": "Nastavit rozložení",
"containers.Edit.Link.Fields": "Upravit pole",
"containers.Edit.Link.Model": "Upravit Typ obsahu",
"containers.EditView.notification.errors": "Formulář obsahuje chyby",
"containers.Home.introduction": "K úpravě vašich záznamů prosím přistupte skrz odkaz v levém menu. Tento zásuvný modul neobsahuje způsob jak upravit nastavení, stále na něm pracujeme.",
"containers.Home.pluginHeaderDescription": "Spravujte své záznamy mocným a intuitivním rozhraním.",
"containers.Home.pluginHeaderTitle": "Správce obsahu",
"containers.List.addAnEntry": "Přidat nový {entity}",
"containers.List.errorFetchRecords": "Chyba",
"containers.List.pluginHeaderDescription": "{label} záznamů nalezeno",
"containers.List.pluginHeaderDescription.singular": "{label} záznam nalezen",
"containers.ListPage.displayedFields": "Zobrazená pole",
"containers.SettingPage.addField": "Přidat nové pole",
"containers.SettingPage.addRelationalField": "Přidat nové relační pole",
"containers.ListSettingsView.modal-form.edit-label": "Upravit popisek",
"containers.EditSettingsView.modal-form.edit-field": "Upravit pole",
"containers.SettingPage.add.field": "Vložit další pole",
"containers.SettingPage.add.relational-field": "Vložit další relační pole",
"containers.SettingPage.attributes": "Pole atributů",
"containers.SettingPage.attributes.description": "Definovat pořadí atributů",
"containers.SettingPage.attributes.description": "Nastavit pořadí atributů",
"containers.SettingPage.editSettings.description": "Přesuňte pole k vybudování rozložení",
"containers.SettingPage.editSettings.title": "Upravit pohled (nastavení)",
"containers.SettingPage.editSettings.entry.title": "Název záznamu",
"containers.SettingPage.editSettings.entry.title.description": "Nastavit zobrazená pole vašeho záznamu",
"containers.SettingPage.listSettings.description": "Změnit nastavení pro tento druh obsahu",
"containers.SettingPage.editSettings.entry.title.description": "Nastavit zobrazená pole záznamu",
"containers.SettingPage.listSettings.description": "Změnit nastavení pro tento Typ obsahu",
"containers.SettingPage.listSettings.title": "Zobrazení seznamu (nastavení)",
"containers.SettingPage.pluginHeaderDescription": "Změnit výchozí nastavení pro tento Druh Obsahu",
"containers.SettingPage.pluginHeaderDescription": "Změnit výchozí nastavení pro tento Typ obsahu",
"containers.SettingPage.relations": "Relační pole",
"containers.SettingPage.settings": "Nastavení",
"containers.SettingPage.layout": "Rozložení",
"containers.SettingPage.view": "Zobrazení",
"containers.EditView.Group.add.new": "PŘIDAT NOVÝ ZÁZNAM",
"containers.SettingViewModel.pluginHeader.title": "Správce Obsahu - {name}",
"containers.EditView.components.missing.singular": "Chybějící komponenty: {count}",
"containers.EditView.components.missing.plural": "Chybějící komponenty: {count}",
"containers.SettingViewModel.pluginHeader.title": "Správce obsahu - {name}",
"containers.SettingsPage.Block.contentType.description": "Upravit specifická nastavení",
"containers.SettingsPage.Block.contentType.title": "Druhy obsahu",
"containers.SettingsPage.Block.generalSettings.description": "Změnit výchozí nastavení pro váše Druhy Obsahu",
"containers.SettingsPage.Block.contentType.title": "Typy obsahu",
"containers.SettingsPage.Block.generalSettings.description": "Změnit výchozí nastavení pro váše Typy obsahu",
"containers.SettingsPage.Block.generalSettings.title": "Obecné",
"containers.SettingsPage.pluginHeaderDescription": "Upravit nastavení všech vašich Druhů obsahu a Skupin",
"containers.SettingsPage.pluginHeaderDescription": "Upravit nastavení všech vašich Typů obsahu a Skupin",
"containers.SettingsView.list.title": "Zobrazit nastavení",
"containers.SettingsView.list.subtitle": "Upravit rozložení a zobrazení vašech druhů obsahu a skupin",
"containers.SettingsView.list.subtitle": "Upravit rozložení a zobrazení vašich Typů obsahu a skupin",
"emptyAttributes.button": "Přejít na tvorbu obsahu",
"emptyAttributes.description": "Přidejte své první pole do tohoto Druhu Obsahu",
"emptyAttributes.description": "Přidejte své první pole do tohoto Typu obsahu",
"emptyAttributes.title": "Zatím zde nejsou žádná pole",
"error.attribute.key.taken": "Hodnota již existuje",
"error.attribute.sameKeyAndName": "Nemůže se rovnat",
"error.attribute.sameKeyAndName": "Hodnoty nesmí být stejné",
"error.attribute.taken": "Pole se stejným názvem již existuje",
"error.contentTypeName.taken": "Tento název již existuje",
"error.model.fetch": "Při pokusu o načtení nastavení modelů došlo k chybě.",
@ -117,7 +135,7 @@
"form.Input.label": "Štítek",
"form.Input.label.inputDescription": "Tato hodnota přepíše štítek zobrazený v hlavičce tabulky",
"form.Input.pageEntries": "Záznamů na stránku",
"form.Input.pageEntries.inputDescription": "Poznámka: Můžete přepsat tuto hodnotu v nastavení Druhů Obsahu.",
"form.Input.pageEntries.inputDescription": "Poznámka: Můžete přepsat tuto hodnotu v nastavení Typů obsahu.",
"form.Input.placeholder": "Moje hodnota",
"form.Input.placeholder.placeholder": "Moje hodnota",
"form.Input.search": "Povolit vyhledávání",
@ -127,7 +145,7 @@
"global.displayedFields": "Zobrazená pole",
"notification.error.displayedFields": "Musíte mít alespoň jedno zobrazené pole",
"notification.error.displayedFields": "Alespoň jedno pole musí být zobrazeno",
"notification.error.relationship.fetch": "Při načítání relačních vazeb došlo k chybě.",
"notification.info.SettingPage.disableSort": "Musíte mít alespoň jeden atribut s povolením řazením.",
"pageNotFound": "Stránka nenalezena",

View File

@ -1,14 +1,15 @@
'use strict';
const { createModelConfigurationSchema } = require('./validation');
const contentTypeService = require('../services/ContentTypes');
const componentService = require('../services/Components');
module.exports = {
/**
* Returns the list of available components
*/
async listComponents(ctx) {
const contentTypeService =
strapi.plugins['content-manager'].services.contenttypes;
const data = Object.keys(strapi.components).map(uid => {
return {
category: strapi.components[uid].category,
@ -35,6 +36,9 @@ module.exports = {
return ctx.notFound('component.notFound');
}
const componentService =
strapi.plugins['content-manager'].services.components;
const data = await componentService.getComponentInformations(uid);
ctx.body = { data };
@ -56,6 +60,11 @@ module.exports = {
return ctx.notFound('component.notFound');
}
const componentService =
strapi.plugins['content-manager'].services.components;
const contentTypeService =
strapi.plugins['content-manager'].services.contenttypes;
const schema = contentTypeService.formatContentTypeSchema(component);
let input;
try {

View File

@ -2,13 +2,15 @@
const _ = require('lodash');
const parseMultipartBody = require('../utils/parse-multipart');
const contentManagerService = require('../services/ContentManager');
module.exports = {
/**
* Returns a list of entities of a content-type matching the query parameters
*/
async find(ctx) {
const contentManagerService =
strapi.plugins['content-manager'].services.contentmanager;
let entities = [];
if (_.has(ctx.request.query, '_q')) {
entities = await contentManagerService.search(
@ -28,6 +30,9 @@ module.exports = {
* Returns an entity of a content type by id
*/
async findOne(ctx) {
const contentManagerService =
strapi.plugins['content-manager'].services.contentmanager;
const entry = await contentManagerService.fetch(ctx.params);
// Entry not found
@ -42,6 +47,9 @@ module.exports = {
* Returns a count of entities of a content type matching query parameters
*/
async count(ctx) {
const contentManagerService =
strapi.plugins['content-manager'].services.contentmanager;
let count;
if (_.has(ctx.request.query, '_q')) {
count = await contentManagerService.countSearch(
@ -61,6 +69,9 @@ module.exports = {
* Creates an entity of a content type
*/
async create(ctx) {
const contentManagerService =
strapi.plugins['content-manager'].services.contentmanager;
const { model } = ctx.params;
try {
@ -96,6 +107,9 @@ module.exports = {
async update(ctx) {
const { id, model } = ctx.params;
const contentManagerService =
strapi.plugins['content-manager'].services.contentmanager;
try {
if (ctx.is('multipart')) {
const { data, files } = parseMultipartBody(ctx);
@ -125,6 +139,9 @@ module.exports = {
* Deletes one entity of a content type matching a query
*/
async delete(ctx) {
const contentManagerService =
strapi.plugins['content-manager'].services.contentmanager;
ctx.body = await contentManagerService.delete(ctx.params);
},
@ -132,6 +149,9 @@ module.exports = {
* Deletes multiple entities of a content type matching a query
*/
async deleteMany(ctx) {
const contentManagerService =
strapi.plugins['content-manager'].services.contentmanager;
ctx.body = await contentManagerService.deleteMany(
ctx.params,
ctx.request.query

View File

@ -1,14 +1,14 @@
'use strict';
const { createModelConfigurationSchema } = require('./validation');
const service = require('../services/ContentTypes');
const componentService = require('../services/Components');
module.exports = {
/**
* Returns the list of available content types
*/
listContentTypes(ctx) {
const service = strapi.plugins['content-manager'].services.contenttypes;
const contentTypes = Object.keys(strapi.contentTypes)
.filter(uid => {
if (uid.startsWith('strapi::')) return false;
@ -42,6 +42,10 @@ module.exports = {
return ctx.notFound('contentType.notFound');
}
const service = strapi.plugins['content-manager'].services.contenttypes;
const componentService =
strapi.plugins['content-manager'].services.components;
const contentTypeConfigurations = await service.getConfiguration(uid);
const data = {
@ -74,6 +78,8 @@ module.exports = {
return ctx.notFound('contentType.notFound');
}
const service = strapi.plugins['content-manager'].services.contenttypes;
const schema = service.formatContentTypeSchema(contentType);
let input;

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -36,7 +36,6 @@ function EditViewButton(props) {
: `${category}/${componentSlug}`;
const handleClick = () => {
// TODO
emitEvent('willEditEditLayout');
props.push(`${baseUrl}/${suffixUrl}`);
};

View File

@ -44,7 +44,24 @@ const AttributeOption = forwardRef(({ tabIndex, type }, ref) => {
const handleClick = () => {
const forTarget = query.get('forTarget');
const targetUid = query.get('targetUid');
const headerDisplayName = query.get('headerDisplayName');
const header_label_1 = query.get('header_label_1');
const header_info_category_1 = query.get('header_info_category_1');
const header_info_name_1 = query.get('header_info_name_1');
const header_label_2 = query.get('header_label_2');
const header_icon_name_2 = query.get('header_icon_name_2');
const header_icon_isCustom_2 = query.get('header_icon_isCustom_2');
const header_info_category_2 = query.get('header_info_category_2');
const header_info_name_2 = query.get('header_info_name_2');
const header_label_3 = query.get('header_label_3');
const header_icon_name_3 = query.get('header_icon_name_3');
const header_icon_isCustom_3 = query.get('header_icon_isCustom_3');
const header_info_category_3 = query.get('header_info_category_3');
const header_info_name_3 = query.get('header_info_name_3');
const header_label_4 = query.get('header_label_4');
const header_icon_name_4 = query.get('header_icon_name_4');
const header_icon_isCustom_4 = query.get('header_icon_isCustom_4');
const header_info_category_4 = query.get('header_info_category_4');
const header_info_name_4 = query.get('header_info_name_4');
const search = makeSearch({
modalType: 'attribute',
@ -53,11 +70,28 @@ const AttributeOption = forwardRef(({ tabIndex, type }, ref) => {
forTarget,
targetUid,
attributeType: type,
headerDisplayName,
step: type === 'component' ? '1' : null,
headerDisplayCategory: query.get('headerDisplayCategory'),
headerDisplaySubCategory: query.get('headerDisplaySubCategory'),
subTargetUid: query.get('subTargetUid'),
header_label_1,
header_info_name_1,
header_info_category_1,
header_label_2,
header_icon_name_2,
header_icon_isCustom_2,
header_info_name_2,
header_info_category_2,
header_label_3,
header_icon_name_3,
header_icon_isCustom_3,
header_info_name_3,
header_info_category_3,
header_label_4,
header_icon_name_4,
header_icon_isCustom_4,
header_info_name_4,
header_info_category_4,
header_icon_isCustom_1: false,
header_icon_name_1: type,
});
if (forTarget === 'contentType') {

View File

@ -15,8 +15,10 @@ import Td from '../Td';
function ComponentList({
customRowComponent,
component,
dzName,
mainTypeName,
isFromDynamicZone,
isNestedInDZComponent,
firstLoopComponentName,
firstLoopComponentUid,
}) {
@ -32,6 +34,7 @@ function ComponentList({
<Td colSpan={12} isChildOfDynamicZone={isFromDynamicZone}>
<List
customRowComponent={customRowComponent}
dzName={dzName}
items={convertAttrObjToArray(attributes)}
targetUid={component}
mainTypeName={mainTypeName}
@ -39,6 +42,7 @@ function ComponentList({
firstLoopComponentUid={firstLoopComponentUid || component}
editTarget="components"
isFromDynamicZone={isFromDynamicZone}
isNestedInDZComponent={isNestedInDZComponent}
isSub
secondLoopComponentName={
firstLoopComponentName ? componentName : null
@ -53,15 +57,19 @@ function ComponentList({
ComponentList.defaultProps = {
component: null,
customRowComponent: null,
dzName: null,
isFromDynamicZone: false,
isNestedInDZComponent: false,
};
ComponentList.propTypes = {
component: PropTypes.string,
customRowComponent: PropTypes.func,
dzName: PropTypes.string,
firstLoopComponentName: PropTypes.string,
firstLoopComponentUid: PropTypes.string,
isFromDynamicZone: PropTypes.bool,
isNestedInDZComponent: PropTypes.bool,
mainTypeName: PropTypes.string.isRequired,
targetUid: PropTypes.string.isRequired,
};

View File

@ -85,6 +85,7 @@ function DynamicZoneList({
<ComponentList
{...props}
isFromDynamicZone
dzName={name}
mainTypeName={mainTypeName}
targetUid={targetUid}
key={component}

View File

@ -6,6 +6,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { useGlobalContext, ListButton } from 'strapi-helper-plugin';
import { Button } from '@buffetjs/core';
import { Plus } from '@buffetjs/icons';
@ -26,34 +27,125 @@ function List({
mainTypeName,
editTarget,
isFromDynamicZone,
isNestedInDZComponent,
isMain,
firstLoopComponentName,
firstLoopComponentUid,
secondLoopComponentName,
secondLoopComponentUid,
isSub,
dzName,
}) {
const { formatMessage } = useGlobalContext();
const { isInDevelopmentMode } = useDataManager();
const { isInDevelopmentMode, modifiedData } = useDataManager();
const { openModalAddField } = useListView();
const onClickAddField = () => {
let headerDisplayName = mainTypeName;
const firstComponentIcon = get(
modifiedData,
['components', firstLoopComponentUid, 'schema', 'icon'],
''
);
const firstComponentCategory = get(
modifiedData,
['components', firstLoopComponentUid, 'category'],
null
);
const firstComponentFriendlyName = get(
modifiedData,
['components', firstLoopComponentUid, 'schema', 'name'],
null
);
const secondComponentCategory = get(
modifiedData,
['components', secondLoopComponentUid, 'category'],
null
);
const secondComponentFriendlyName = get(
modifiedData,
['components', secondLoopComponentUid, 'schema', 'name'],
null
);
const secondComponentIcon = get(
modifiedData,
['components', secondLoopComponentUid, 'schema', 'icon'],
''
);
let firstHeaderObject = {
header_label_1: mainTypeName,
header_icon_name_1: editTarget,
header_icon_isCustom_1: false,
header_info_category_1: null,
header_info_name_1: null,
};
let secondHeaderObject = {
header_label_2: firstLoopComponentName,
header_icon_name_2: 'component',
header_icon_isCustom_2: false,
header_info_category_2: firstComponentCategory,
header_info_name_2: firstComponentFriendlyName,
};
let thirdHeaderObject = {
header_icon_name_3: 'component',
header_icon_isCustom_3: false,
header_info_category_3: secondComponentCategory,
header_info_name_3: secondComponentFriendlyName,
};
let fourthHeaderObject = {
header_icon_name_4: null,
header_icon_isCustom_4: false,
header_info_category_4: secondComponentCategory,
header_info_name_4: secondComponentFriendlyName,
};
if (firstLoopComponentName) {
headerDisplayName = firstLoopComponentName;
firstHeaderObject = {
...firstHeaderObject,
header_icon_name_1: firstComponentIcon,
header_icon_isCustom_1: true,
};
}
if (secondLoopComponentUid) {
headerDisplayName = secondLoopComponentName;
firstHeaderObject = {
...firstHeaderObject,
header_icon_name_1: secondComponentIcon,
header_icon_isCustom_1: true,
};
thirdHeaderObject = {
...thirdHeaderObject,
header_label_3: secondLoopComponentName,
};
}
if (isFromDynamicZone || isNestedInDZComponent) {
secondHeaderObject = {
...secondHeaderObject,
header_label_2: dzName,
header_icon_name_2: 'dynamiczone',
header_icon_isCustom_2: false,
header_info_category_2: null,
header_info_name_2: null,
};
thirdHeaderObject = {
...thirdHeaderObject,
header_icon_name_3: isNestedInDZComponent ? 'component' : null,
header_label_3: firstLoopComponentName,
header_info_category_3: firstComponentCategory,
header_info_name_3: firstComponentFriendlyName,
};
fourthHeaderObject = {
...fourthHeaderObject,
header_label_4: secondLoopComponentName,
};
}
openModalAddField(
editTarget,
targetUid,
headerDisplayName,
firstLoopComponentUid ? mainTypeName : null,
secondLoopComponentName ? firstLoopComponentName : null,
secondLoopComponentUid ? firstLoopComponentUid : null
firstHeaderObject,
secondHeaderObject,
thirdHeaderObject,
fourthHeaderObject
);
};
@ -87,8 +179,9 @@ function List({
<React.Fragment key={item.name}>
<CustomRow
{...item}
dzName={dzName}
isNestedInDZComponent={isNestedInDZComponent}
targetUid={targetUid}
// NEW props
mainTypeName={mainTypeName}
editTarget={editTarget}
firstLoopComponentName={firstLoopComponentName}
@ -103,8 +196,8 @@ function List({
{...item}
customRowComponent={customRowComponent}
targetUid={targetUid}
// NEW PROPS
dzName={dzName}
isNestedInDZComponent={isFromDynamicZone}
mainTypeName={mainTypeName}
editTarget={editTarget}
firstLoopComponentName={firstLoopComponentName}
@ -153,9 +246,11 @@ List.defaultProps = {
addComponentToDZ: () => {},
className: null,
customRowComponent: null,
dzName: null,
firstLoopComponentName: null,
firstLoopComponentUid: null,
isFromDynamicZone: false,
isNestedInDZComponent: false,
isMain: false,
isSub: false,
items: [],
@ -168,10 +263,12 @@ List.propTypes = {
addComponentToDZ: PropTypes.func,
className: PropTypes.string,
customRowComponent: PropTypes.func,
dzName: PropTypes.string,
editTarget: PropTypes.string.isRequired,
firstLoopComponentName: PropTypes.string,
firstLoopComponentUid: PropTypes.string,
isFromDynamicZone: PropTypes.bool,
isNestedInDZComponent: PropTypes.bool,
isMain: PropTypes.bool,
items: PropTypes.instanceOf(Array),
mainTypeName: PropTypes.string.isRequired,

View File

@ -6,6 +6,7 @@ import { AttributeIcon } from '@buffetjs/core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import pluginId from '../../pluginId';
import useDataManager from '../../hooks/useDataManager';
import getAttributeDisplayedType from '../../utils/getAttributeDisplayedType';
import getTrad from '../../utils/getTrad';
import Curve from '../../icons/Curve';
import UpperFist from '../UpperFirst';
@ -14,6 +15,7 @@ import Wrapper from './Wrapper';
function ListRow({
configurable,
name,
dzName,
nature,
onClick,
plugin,
@ -28,12 +30,15 @@ function ListRow({
repeatable,
secondLoopComponentName,
secondLoopComponentUid,
isNestedInDZComponent,
}) {
const {
contentTypes,
isInDevelopmentMode,
modifiedData,
removeAttribute,
} = useDataManager();
const ico = ['integer', 'biginteger', 'float', 'decimal'].includes(type)
? 'number'
: type;
@ -54,15 +59,120 @@ function ListRow({
const handleClick = () => {
if (configurable !== false) {
const firstComponentCategory = get(
modifiedData,
['components', firstLoopComponentUid, 'category'],
null
);
const secondComponentCategory = get(
modifiedData,
['components', secondLoopComponentUid, 'category'],
null
);
const attrType = nature ? 'relation' : type;
let headerDisplayName = mainTypeName;
const icoType = getAttributeDisplayedType(attrType);
let firstHeaderObject = {
header_label_1: mainTypeName,
header_icon_name_1: icoType,
header_icon_isCustom_1: false,
header_info_category_1: null,
header_info_name_1: null,
};
let secondHeaderObject = {
header_label_2: name,
header_icon_name_2: null,
header_icon_isCustom_2: false,
header_info_category_2: null,
header_info_name_2: null,
};
let thirdHeaderObject = {
header_icon_name_3: 'component',
header_icon_isCustom_3: false,
header_info_category_3: null,
header_info_name_3: null,
};
let fourthHeaderObject = {
header_icon_name_4: null,
header_icon_isCustom_4: false,
header_info_category_4: null,
header_info_name_4: null,
};
let fifthHeaderObject = {
header_icon_name_5: null,
header_icon_isCustom_5: false,
header_info_category_5: null,
header_info_name_5: null,
};
if (firstLoopComponentName) {
headerDisplayName = firstLoopComponentName;
secondHeaderObject = {
header_label_2: firstLoopComponentName,
header_icon_name_2: 'component',
header_icon_isCustom_2: false,
header_info_category_2: firstComponentCategory,
header_info_name_2: firstLoopComponentName,
};
thirdHeaderObject = {
...thirdHeaderObject,
header_label_3: name,
header_icon_name_3: null,
};
}
if (secondLoopComponentUid) {
headerDisplayName = secondLoopComponentName;
thirdHeaderObject = {
...thirdHeaderObject,
header_label_3: secondLoopComponentName,
header_icon_name_3: 'component',
header_info_category_3: secondComponentCategory,
header_info_name_3: secondLoopComponentName,
};
fourthHeaderObject = {
...fourthHeaderObject,
header_label_4: name,
header_icon_name_4: null,
};
}
if (isFromDynamicZone || isNestedInDZComponent) {
secondHeaderObject = {
header_label_2: dzName,
header_icon_name_2: 'dynamiczone',
header_icon_isCustom_2: false,
header_info_name_2: null,
header_info_category_2: null,
};
thirdHeaderObject = {
header_icon_name_3: 'component',
header_label_3: firstLoopComponentName,
header_info_name_3: firstComponentCategory,
header_info_category_3: firstComponentCategory,
};
if (!isNestedInDZComponent) {
fourthHeaderObject = {
header_icon_name_4: null,
header_icon_isCustom_4: false,
header_info_category_4: null,
header_label_4: name,
};
} else {
fourthHeaderObject = {
header_icon_name_4: 'components',
header_icon_isCustom_4: false,
header_info_category_4: secondComponentCategory,
header_info_name_4: secondLoopComponentName,
header_label_4: secondLoopComponentName,
};
fifthHeaderObject = {
...fifthHeaderObject,
header_label_5: name,
};
}
}
onClick(
@ -74,10 +184,11 @@ function ListRow({
name,
// Type of the attribute
attrType,
headerDisplayName,
firstLoopComponentUid ? mainTypeName : null,
secondLoopComponentName ? firstLoopComponentName : null,
secondLoopComponentUid ? firstLoopComponentUid : null
firstHeaderObject,
secondHeaderObject,
thirdHeaderObject,
fourthHeaderObject,
fifthHeaderObject
);
}
};
@ -171,9 +282,11 @@ function ListRow({
ListRow.defaultProps = {
configurable: true,
dzName: null,
firstLoopComponentName: null,
firstLoopComponentUid: null,
isFromDynamicZone: false,
isNestedInDZComponent: false,
nature: null,
onClick: () => {},
onClickDelete: () => {},
@ -188,10 +301,12 @@ ListRow.defaultProps = {
ListRow.propTypes = {
configurable: PropTypes.bool,
dzName: PropTypes.string,
editTarget: PropTypes.string.isRequired,
firstLoopComponentName: PropTypes.string,
firstLoopComponentUid: PropTypes.string,
isFromDynamicZone: PropTypes.bool,
isNestedInDZComponent: PropTypes.bool,
mainTypeName: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
nature: PropTypes.string,

View File

@ -1,33 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import { get, upperFirst } from 'lodash';
import ComponentInfosWrapper from './ComponentInfosWrapper';
import useDataManager from '../../hooks/useDataManager';
import UpperFirst from '../UpperFirst';
import ComponentInfosWrapper from './ComponentInfosWrapper';
const ComponentInfos = ({ uid }) => {
// We might want to change to initialData...
// @Aurelsicoko
const { modifiedData } = useDataManager();
const currentComponent = get(modifiedData, ['components', uid], {});
const currentComponentCategory = get(currentComponent, 'category', '');
const currentComponentFriendlyName = get(
currentComponent,
['schema', 'name'],
''
);
const ComponentInfos = ({ category, name }) => {
return (
<ComponentInfosWrapper>
&nbsp; ({upperFirst(currentComponentCategory)}
&nbsp;&nbsp;
<UpperFirst content={currentComponentFriendlyName} />)
&nbsp; (<UpperFirst content={category} /> &nbsp;&nbsp;
<UpperFirst content={name} />)
</ComponentInfosWrapper>
);
};
ComponentInfos.propTypes = {
uid: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
};
export default ComponentInfos;

View File

@ -0,0 +1,47 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import UpperFirst from '../UpperFirst';
import Icon from './Icon';
import Item from './Item';
import Menu from './Menu';
import Toggle from './Toggle';
import Wrapper from './Wrapper';
const DropdownInfos = ({ headers, shouldDisplaySecondHeader }) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const toggle = () => setDropdownOpen(prevState => !prevState);
return (
<Wrapper isOpen={dropdownOpen} toggle={toggle} style={{ margin: 'auto 0' }}>
<Toggle>...</Toggle>
<Menu style={{ top: '8px' }}>
{headers.map((header, index) => {
if (!shouldDisplaySecondHeader && index === 1) {
return null;
}
return (
<Item key={index}>
<Icon type={header.icon.name} />
<span>
<UpperFirst content={header.label} />
</span>
</Item>
);
})}
</Menu>
</Wrapper>
);
};
DropdownInfos.defaultProps = {
headers: [],
shouldDisplaySecondHeader: false,
};
DropdownInfos.propTypes = {
headers: PropTypes.array,
shouldDisplaySecondHeader: PropTypes.bool,
};
export default DropdownInfos;

View File

@ -1,69 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import UpperFirst from '../UpperFirst';
import ComponentInfos from './ComponentInfos';
import IconWrapper from './IconWrapper';
const Header = ({
category,
name,
subCategory,
target,
targetUid,
subTargetUid,
}) => {
const shouldDisplayComponentCatInfos = target === 'components';
const content = (
<>
<span>
<UpperFirst content={category} />
</span>
<IconWrapper>
<FontAwesomeIcon icon="chevron-right" />
</IconWrapper>
{subCategory && (
<>
<span>
<UpperFirst content={subCategory} />
</span>
<ComponentInfos uid={subTargetUid} />
<IconWrapper>
<FontAwesomeIcon icon="chevron-right" />
</IconWrapper>
</>
)}
</>
);
return (
<>
{category && content}
<span>
<UpperFirst content={name} />
</span>
{shouldDisplayComponentCatInfos && <ComponentInfos uid={targetUid} />}
</>
);
};
Header.defaultProps = {
category: null,
name: null,
subCategory: null,
subTargetUid: null,
target: null,
targetUid: null,
};
Header.propTypes = {
category: PropTypes.string,
name: PropTypes.string,
subCategory: PropTypes.string,
subTargetUid: PropTypes.string,
target: PropTypes.string,
targetUid: PropTypes.string,
};
export default Header;

View File

@ -0,0 +1,13 @@
import React from 'react';
import { AttributeIcon } from '@buffetjs/core';
import PropTypes from 'prop-types';
const Icon = ({ type }) => (
<AttributeIcon type={type} style={{ margin: 'auto 20px auto 0' }} />
);
Icon.propTypes = {
type: PropTypes.string.isRequired,
};
export default Icon;

View File

@ -0,0 +1,19 @@
import styled from 'styled-components';
import { DropdownItem } from 'reactstrap';
const Item = styled(DropdownItem)`
display: flex;
padding-left: 10px;
padding-right: 10px;
color: #3b3b3b;
font-weight: 600;
font-size: 14px;
&:active,
&:focus,
&:hover {
background-color: transparent;
outline: 0;
}
`;
export default Item;

View File

@ -0,0 +1,8 @@
import styled from 'styled-components';
import { DropdownMenu } from 'reactstrap';
const Menu = styled(DropdownMenu)`
top: 8px;
`;
export default Menu;

View File

@ -0,0 +1,14 @@
import styled from 'styled-components';
import { DropdownToggle } from 'reactstrap';
const Toggle = styled(DropdownToggle)`
height: 12px;
background: transparent;
border: 0;
margin-top: -14px;
color: #3b3b3b;
font-weight: 600;
font-size: 14px;
`;
export default Toggle;

View File

@ -0,0 +1,12 @@
import styled from 'styled-components';
import { Dropdown } from 'reactstrap';
const Wrapper = styled(Dropdown)`
.dropdown-menu {
top: 8px !important;
box-shadow: 0 2px 4px #e3e9f3;
border: 0;
}
`;
export default Wrapper;

View File

@ -1,89 +1,103 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { HeaderModalTitle } from 'strapi-helper-plugin';
import { get } from 'lodash';
import { AttributeIcon } from '@buffetjs/core';
import { FormattedMessage } from 'react-intl';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import useDataManager from '../../hooks/useDataManager';
import pluginId from '../../pluginId';
import ComponentIcon from './ComponentIcon';
import Header from './Header';
import ComponentInfos from './ComponentInfos';
import Icon from './Icon';
import IconWrapper from './IconWrapper';
import UpperFirst from '../UpperFirst';
import DropdownInfos from './DropdownInfos';
const ModalHeader = ({
category,
headerId,
iconType,
name,
target,
targetUid,
subCategory,
subTargetUid,
}) => {
const { modifiedData } = useDataManager();
const currentComponent = get(modifiedData, ['components', targetUid], {});
const shouldDisplayComponentCatInfos = target === 'components';
const currentComponentIcon = get(currentComponent, ['schema', 'icon'], '');
let iconName;
if (iconType === 'components') {
iconName = 'component';
} else {
iconName = iconType;
}
const ModalHeader = ({ headerId, headers }) => {
const shouldDisplayDropDown = headers.length > 3;
return (
<section>
<HeaderModalTitle style={{ textTransform: 'none' }}>
{shouldDisplayComponentCatInfos ? (
<ComponentIcon isSelected>
<FontAwesomeIcon icon={currentComponentIcon} />
</ComponentIcon>
) : (
<AttributeIcon
type={iconName}
style={{ margin: 'auto 20px auto 0' }}
/>
)}
{headerId && (
<FormattedMessage id={`${pluginId}.${headerId}`} values={{ name }} />
)}
{!headerId && (
<Header
category={category}
name={name}
target={target}
targetUid={targetUid}
subCategory={subCategory}
subTargetUid={subTargetUid}
/>
<>
<Icon type={get(headers, [0, 'icon', 'name'], '')} />
<FormattedMessage
id={headerId}
values={{ name: get(headers, [0, 'label'], '') }}
/>
</>
)}
{!headerId &&
headers.map((header, index) => {
const iconName = get(header, ['icon', 'name'], '');
const iconType = iconName === null ? '' : iconName;
const icon = get(header, ['icon', 'isCustom'], false) ? (
<ComponentIcon isSelected>
<FontAwesomeIcon icon={iconType} />
</ComponentIcon>
) : (
<Icon type={iconType} />
);
if (shouldDisplayDropDown && index === 1) {
return (
<Fragment key={index}>
<IconWrapper>
<FontAwesomeIcon icon="chevron-right" />
</IconWrapper>
<DropdownInfos
headers={[headers[1], headers[2]]}
shouldDisplaySecondHeader={headers.length > 4}
/>
</Fragment>
);
}
if (shouldDisplayDropDown && index === 2 && headers.length > 4) {
return null;
}
if (index === 0) {
return (
<Fragment key={index}>
{icon}
<span>
<UpperFirst content={get(header, ['label'], '')} />
</span>
</Fragment>
);
}
return (
<Fragment key={index}>
<IconWrapper>
<FontAwesomeIcon icon="chevron-right" />
</IconWrapper>
<span>
<UpperFirst content={get(header, ['label'], '')} />
</span>
{header.info.category && (
<ComponentInfos
category={header.info.category}
name={header.info.name}
/>
)}
</Fragment>
);
})}
</HeaderModalTitle>
</section>
);
};
ModalHeader.defaultProps = {
category: null,
headerId: '',
iconType: 'contentType',
name: '',
target: null,
targetUid: null,
subCategory: null,
subTargetUid: null,
headers: [],
};
ModalHeader.propTypes = {
category: PropTypes.string,
headerId: PropTypes.string,
iconType: PropTypes.string,
name: PropTypes.string,
target: PropTypes.string,
targetUid: PropTypes.string,
subCategory: PropTypes.string,
subTargetUid: PropTypes.string,
headers: PropTypes.array,
};
export default ModalHeader;

View File

@ -1,5 +1,6 @@
import { fromJS, OrderedMap } from 'immutable';
import { get, has } from 'lodash';
import retrieveComponentsFromSchema from './utils/retrieveComponentsFromSchema';
import makeUnique from '../../utils/makeUnique';
const initialState = fromJS({
@ -24,6 +25,47 @@ const getOppositeNature = originalNature => {
}
};
const addComponentsToState = (state, componentToAddUid, objToUpdate) => {
let newObj = objToUpdate;
const componentToAdd = state.getIn(['components', componentToAddUid]);
const isTemporaryComponent = componentToAdd.get('isTemporary');
const componentToAddSchema = componentToAdd.getIn(['schema', 'attributes']);
const hasComponentAlreadyBeenAdded =
state.getIn(['modifiedData', 'components', componentToAddUid]) !==
undefined;
// created components are already in the modifiedData.components
// We don't add them because all modifications will be lost
if (isTemporaryComponent || hasComponentAlreadyBeenAdded) {
return newObj;
}
// Add the added components to the modifiedData.compontnes
newObj = newObj.set(componentToAddUid, componentToAdd);
const nestedComponents = retrieveComponentsFromSchema(
componentToAddSchema.toJS(),
state.get('components').toJS()
);
// We need to add the nested components to the modifiedData.components as well
nestedComponents.forEach(componentUid => {
const isTemporary =
state.getIn(['components', componentUid, 'isTemporary']) || false;
const hasNestedComponentAlreadyBeenAdded =
state.getIn(['modifiedData', 'components', componentUid]) !== undefined;
// Same logic here otherwise we will lose the modifications added to the components
if (!isTemporary && !hasNestedComponentAlreadyBeenAdded) {
newObj = newObj.set(
componentUid,
state.getIn(['components', componentUid])
);
}
});
return newObj;
};
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_ATTRIBUTE': {
@ -86,12 +128,7 @@ const reducer = (state, action) => {
)
.updateIn(['modifiedData', 'components'], existingCompos => {
if (action.shouldAddComponentToData) {
const componentToAdd = state.getIn(['components', rest.component]);
return existingCompos.update(
componentToAdd.get('uid'),
() => componentToAdd
);
return addComponentsToState(state, rest.component, existingCompos);
}
return existingCompos;
@ -138,15 +175,7 @@ const reducer = (state, action) => {
)
.updateIn(['modifiedData', 'components'], old => {
const componentsSchema = newComponents.reduce((acc, current) => {
const addedCompoSchema = state.getIn(['components', current]);
const isTemporaryComponent = addedCompoSchema.get('isTemporary');
// created components are already in the modifiedData.components
if (isTemporaryComponent) {
return acc;
}
return acc.set(current, addedCompoSchema);
return addComponentsToState(state, current, acc);
}, old);
return componentsSchema;

View File

@ -31,6 +31,8 @@ import getTrad from '../../utils/getTrad';
import makeSearch from '../../utils/makeSearch';
import getAttributes from './utils/attributes';
import forms from './utils/forms';
import createHeadersArray from './utils/createHeadersArray';
import createHeadersObjectFromArray from './utils/createHeadersObjectFromArray';
import { createComponentUid, createUid } from './utils/createUid';
import getModalTitleSubHeader from './utils/getModalTitleSubHeader';
import getNextSearch from './utils/getNextSearch';
@ -75,18 +77,40 @@ const FormModal = () => {
useEffect(() => {
if (!isEmpty(search)) {
const actionType = query.get('actionType');
// Return 'null' if there isn't any attributeType search params
// Returns 'null' if there isn't any attributeType search params
const attributeName = query.get('attributeName');
const attributeType = query.get('attributeType');
const dynamicZoneTarget = query.get('dynamicZoneTarget');
const forTarget = query.get('forTarget');
const headerDisplayCategory = query.get('headerDisplayCategory');
const headerDisplayName = query.get('headerDisplayName');
const headerDisplaySubCategory = query.get('headerDisplaySubCategory');
const modalType = query.get('modalType');
const targetUid = query.get('targetUid');
const settingType = query.get('settingType');
const subTargetUid = query.get('subTargetUid');
const headerId = query.get('headerId');
const header_label_1 = query.get('header_label_1');
const header_icon_name_1 = query.get('header_icon_name_1');
const header_icon_isCustom_1 = query.get('header_icon_isCustom_1');
const header_info_category_1 = query.get('header_info_category_1');
const header_info_name_1 = query.get('header_info_name_1');
const header_label_2 = query.get('header_label_2');
const header_icon_name_2 = query.get('header_icon_name_2');
const header_icon_isCustom_2 = query.get('header_icon_isCustom_2');
const header_info_category_2 = query.get('header_info_category_2');
const header_info_name_2 = query.get('header_info_name_2');
const header_label_3 = query.get('header_label_3');
const header_icon_name_3 = query.get('header_icon_name_3');
const header_icon_isCustom_3 = query.get('header_icon_isCustom_3');
const header_info_category_3 = query.get('header_info_category_3');
const header_info_name_3 = query.get('header_info_name_3');
const header_label_4 = query.get('header_label_4');
const header_icon_name_4 = query.get('header_icon_name_4');
const header_icon_isCustom_4 = query.get('header_icon_isCustom_4');
const header_info_category_4 = query.get('header_info_category_4');
const header_info_name_4 = query.get('header_info_name_4');
const header_label_5 = query.get('header_label_5');
const header_icon_name_5 = query.get('header_icon_name_5');
const header_icon_isCustom_5 = query.get('header_icon_isCustom_5');
const header_info_category_5 = query.get('header_info_category_5');
const header_info_name_5 = query.get('header_info_name_5');
const step = query.get('step');
const pathToSchema =
forTarget === 'contentType' || forTarget === 'component'
@ -98,16 +122,38 @@ const FormModal = () => {
attributeName,
attributeType,
dynamicZoneTarget,
headerDisplayName,
headerDisplayCategory,
headerDisplaySubCategory,
forTarget,
modalType,
pathToSchema,
settingType,
subTargetUid,
step,
targetUid,
header_label_1,
header_icon_name_1,
header_icon_isCustom_1,
header_info_name_1,
header_info_category_1,
header_label_2,
header_icon_name_2,
header_icon_isCustom_2,
header_info_name_2,
header_info_category_2,
header_label_3,
header_icon_name_3,
header_icon_isCustom_3,
header_info_name_3,
header_info_category_3,
header_label_4,
header_icon_name_4,
header_icon_isCustom_4,
header_info_name_4,
header_info_category_4,
header_label_5,
header_icon_name_5,
header_icon_isCustom_5,
header_info_name_5,
header_info_category_5,
headerId,
});
// Reset all the modification when opening the edit category modal
@ -280,19 +326,7 @@ const FormModal = () => {
const form = get(forms, [state.modalType, 'form', state.settingType], () => ({
items: [],
}));
// TODO improve icon logic
let iconType = ['component', 'contentType'].includes(state.modalType)
? state.modalType
: state.forTarget;
if (state.modalType === 'addComponentToDynamicZone') {
iconType = 'dynamiczone';
}
if (state.modalType === 'editCategory') {
iconType = 'component';
}
const headers = createHeadersArray(state);
const isCreatingContentType = state.modalType === 'contentType';
const isCreatingComponent = state.modalType === 'component';
@ -310,14 +344,6 @@ const FormModal = () => {
const isPickingAttribute = state.modalType === 'chooseAttribute';
const uid = createUid(modifiedData.name || '');
let headerId = isCreating
? `modalForm.${state.modalType}.header-create`
: 'modalForm.header-edit';
if (!['contentType', 'component'].includes(state.modalType)) {
headerId = null;
}
const checkFormValidity = async () => {
let schema;
const dataToValidate =
@ -532,6 +558,7 @@ const FormModal = () => {
...rest,
});
};
const handleSubmit = async (e, shouldContinue = isCreating) => {
e.preventDefault();
@ -541,17 +568,21 @@ const FormModal = () => {
const targetUid =
state.forTarget === 'components' ? state.targetUid : uid;
// TODO REMOVE and use makeSearch
const createNextSearch = searchUid => {
if (!shouldContinue) {
return '';
}
const headerIcon = ['contentType', 'component'].includes(state.forTarget)
? state.forTarget
: get(
allDataSchema,
['components', state.targetUid, 'schema', 'icon'],
''
);
return `modalType=chooseAttribute&forTarget=${
state.forTarget
}&targetUid=${searchUid}&headerDisplayName=${state.headerDisplayName ||
modifiedData.name}`;
};
// Remove the last header when editing
if (state.actionType === 'edit') {
headers.pop();
}
const headersObject = createHeadersObjectFromArray(headers);
const nextHeaderIndex = headers.length + 1;
if (isCreatingContentType) {
// Create the content type schema
@ -571,7 +602,9 @@ const FormModal = () => {
modalType: 'chooseAttribute',
forTarget: state.forTarget,
targetUid,
headerDisplayName: modifiedData.name,
header_label_1: modifiedData.name,
header_icon_name_1: 'contentType',
header_icon_isCustom_1: null,
}),
});
} else if (isCreatingComponent) {
@ -590,7 +623,9 @@ const FormModal = () => {
modalType: 'chooseAttribute',
forTarget: state.forTarget,
targetUid: componentUid,
headerDisplayName: modifiedData.name,
header_label_1: modifiedData.name,
header_icon_name_1: 'contentType',
header_icon_isCustom_1: null,
}),
pathname: `/plugins/${pluginId}/component-categories/${category}/${componentUid}`,
});
@ -604,6 +639,7 @@ const FormModal = () => {
}
} else if (isEditingCategory) {
if (toLower(initialData.name) === toLower(modifiedData.name)) {
// Close the modal
push({ search: '' });
return;
@ -629,20 +665,19 @@ const FormModal = () => {
// Adding a component to a dynamiczone is not the same logic as creating a simple field
// so the search is different
// For the modal header
const displayCategory = state.headerDisplayName;
const displayName = modifiedData.name;
const dzSearch = makeNextSearch({
modalType: 'addComponentToDynamicZone',
forTarget: 'contentType',
targetUid: state.targetUid,
headerDisplayName: displayName,
headerDisplayCategory: displayCategory,
dynamicZoneTarget: modifiedData.name,
settingType: 'base',
step: '1',
actionType: 'create',
...headersObject,
header_label_2: modifiedData.name,
header_icon_name_2: null,
header_icon_isCustom_2: false,
});
const nextSearch = isDynamicZoneAttribute
? dzSearch
@ -651,11 +686,12 @@ const FormModal = () => {
modalType: 'chooseAttribute',
forTarget: state.forTarget,
targetUid,
headerDisplayName: state.headerDisplayName,
headerDisplayCategory: state.headerDisplayCategory,
// keep the old state
headerDisplaySubCategory: state.headerDisplaySubCategory,
subTargetUid: state.subTargetUid,
...headersObject,
header_icon_isCustom_1: ![
'contentType',
'component',
].includes(state.forTarget),
header_icon_name_1: headerIcon,
},
shouldContinue
);
@ -680,7 +716,6 @@ const FormModal = () => {
} else {
if (isInFirstComponentStep) {
// Navigate the user to step 2
// TODO refacto
const nextSearchObj = {
modalType: 'attribute',
actionType: state.actionType,
@ -688,8 +723,12 @@ const FormModal = () => {
forTarget: state.forTarget,
targetUid: state.targetUid,
attributeType: 'component',
headerDisplayName: state.headerDisplayName,
step: '2',
...headersObject,
header_icon_isCustom_1: !['contentType', 'component'].includes(
state.forTarget
),
header_icon_name_1: headerIcon,
};
push({
@ -723,9 +762,18 @@ const FormModal = () => {
// This way we can add fields to the added component (if it wasn't there already)
true
);
const nextSearch = {
modalType: 'chooseAttribute',
forTarget: state.forTarget,
targetUid: state.targetUid,
...headersObject,
header_icon_isCustom_1: !['contentType', 'component'].includes(
state.forTarget
),
header_icon_name_1: headerIcon,
};
// TODO change the search so the modal header is kept
push({ search: shouldContinue ? createNextSearch(targetUid) : '' });
push({ search: makeSearch(nextSearch, shouldContinue) });
// We don't need to end the loop here we want the reducer to be reinitialised
}
@ -736,7 +784,6 @@ const FormModal = () => {
// even though the user didn't set any field
// We need to prevent the component from being created if the user closes the modal at step 2 without any submission
} else if (isCreatingAttribute && isCreatingComponentFromAView) {
const { headerDisplayCategory } = state;
// Step 1
if (isInFirstComponentStep) {
// Here the search could be refactored since it is the same as the case from above
@ -749,18 +796,12 @@ const FormModal = () => {
forTarget: state.forTarget,
targetUid: state.targetUid,
attributeType: 'component',
headerDisplayName: state.headerDisplayName,
step: '2',
...headersObject,
header_icon_isCustom_1: false,
header_icon_name_1: 'component',
};
// Modify the searchObj for the modal header
// This case is happening when creating a nestedComponent after creating a component
if (headerDisplayCategory) {
searchObj.headerDisplayCategory = state.headerDisplayCategory;
searchObj.headerDisplayName = state.headerDisplayName;
searchObj.targetUid = state.targetUid;
}
emitEvent('willCreateComponentFromAttributesModal');
push({
@ -812,21 +853,16 @@ const FormModal = () => {
modalType: 'chooseAttribute',
forTarget: 'components',
targetUid: componentUid,
headerDisplayName: modifiedData.name,
headerDisplayCategory:
state.headerDisplayCategory || state.headerDisplayName,
...headersObject,
header_icon_isCustom_1: true,
header_icon_name_1: componentToCreate.icon,
[`header_label_${nextHeaderIndex}`]: modifiedData.name,
[`header_icon_name_${nextHeaderIndex}`]: 'component',
[`header_icon_isCustom_${nextHeaderIndex}`]: false,
[`header_info_category_${nextHeaderIndex}`]: category,
[`header_info_name_${nextHeaderIndex}`]: componentToCreate.name,
};
// Then we inverse the headerDisplayName because it becomes the last one displayed
// The case is created a component then created a nested one
if (headerDisplayCategory) {
// This is allows to modify the modal header
searchToOpenModalAttributeToAddAttributesToAComponent.headerDisplaySubCategory =
state.headerDisplayName;
searchToOpenModalAttributeToAddAttributesToAComponent.subTargetUid =
state.targetUid;
}
push({
search: makeNextSearch(
searchToOpenModalAttributeToAddAttributesToAComponent,
@ -869,13 +905,19 @@ const FormModal = () => {
// The Dynamic Zone and the component is created created
// Open the modal to add fields to the created component
// TODO search for modal header
const searchToOpenAddField = {
modalType: 'chooseAttribute',
forTarget: 'components',
targetUid: componentUid,
headerDisplayName: modifiedData.componentToCreate.name,
headerDisplayCategory: state.headerDisplayCategory,
...headersObject,
header_icon_isCustom_1: true,
header_icon_name_1: modifiedData.componentToCreate.icon,
[`header_label_${nextHeaderIndex}`]: modifiedData.name,
[`header_icon_name_${nextHeaderIndex}`]: 'component',
[`header_icon_isCustom_${nextHeaderIndex}`]: false,
[`header_info_category_${nextHeaderIndex}`]: category,
[`header_info_name_${nextHeaderIndex}`]: modifiedData
.componentToCreate.name,
};
push({ search: makeSearch(searchToOpenAddField, true) });
@ -901,7 +943,6 @@ const FormModal = () => {
type: 'RESET_PROPS',
});
} catch (err) {
console.log({ err });
const errors = getYupInnerErrors(err);
dispatch({
@ -986,16 +1027,7 @@ const FormModal = () => {
withoverflow={toString(state.modalType === 'addComponentToDynamicZone')}
>
<HeaderModal>
<ModalHeader
name={state.headerDisplayName}
category={state.headerDisplayCategory}
headerId={headerId}
iconType={iconType || 'contentType'}
subCategory={state.headerDisplaySubCategory}
subTargetUid={state.subTargetUid}
target={state.forTarget}
targetUid={state.targetUid}
/>
<ModalHeader headerId={state.headerId} headers={headers} />
<section>
<HeaderModalTitle>
<FormattedMessage
@ -1160,7 +1192,7 @@ const FormModal = () => {
return (
<RelationForm
key="relation"
mainBoxHeader={state.headerDisplayName}
mainBoxHeader={get(headers, [0, 'label'], '')}
modifiedData={modifiedData}
naturePickerType={state.forTarget}
onChange={handleChange}

View File

@ -0,0 +1,37 @@
import { set } from 'lodash';
const ALLOWED_KEYS = [
'header_label',
'header_icon_name',
'header_icon_isCustom',
'header_info_category',
'header_info_name',
];
const createHeadersArray = obj => {
const array = Object.keys(obj).reduce((acc, current) => {
const splitted = current.split('_');
const currentKeys = splitted.filter((_, i) => i !== splitted.length - 1);
if (ALLOWED_KEYS.includes(currentKeys.join('_'))) {
const currentKeysIndex = parseInt(splitted[splitted.length - 1] - 1, 10);
const path = [
currentKeysIndex,
...currentKeys.filter(key => key !== 'header'),
];
let value = obj[current];
if (current.includes('isCustom')) {
value = obj[current] === 'true';
}
set(acc, path, value);
}
return acc;
}, []);
return array.filter(obj => obj.label !== null);
};
export default createHeadersArray;

View File

@ -0,0 +1,27 @@
import { isObject } from 'lodash';
const createHeadersObjectFromArray = array => {
return array.reduce((acc, current, index) => {
const createHeaderObject = (obj, i, middle = '') =>
Object.keys(obj).reduce((acc1, current1) => {
if (isObject(obj[current1])) {
return {
...acc1,
...createHeaderObject(obj[current1], i, `_${current1}`),
};
}
const name = `header${middle}_${current1}_${i}`;
acc1[name] = obj[current1];
return acc1;
}, {});
const headerObject = createHeaderObject(current, index + 1);
return { ...acc, ...headerObject };
}, {});
};
export default createHeadersObjectFromArray;

View File

@ -6,15 +6,37 @@ const INITIAL_STATE_DATA = {
attributeType: null,
dynamicZoneTarget: null,
forTarget: null,
headerDisplayCategory: null,
headerDisplayName: null,
headerDisplaySubCategory: null,
modalType: null,
pathToSchema: [],
settingType: null,
step: null,
subTargetUid: null,
targetUid: null,
headerId: null,
header_label_1: null,
header_icon_name_1: null,
header_icon_isCustom_1: null,
header_info_category_1: null,
header_info_name_1: null,
header_label_2: null,
header_icon_name_2: null,
header_icon_isCustom_2: null,
header_info_category_2: null,
header_info_name_2: null,
header_label_3: null,
header_icon_name_3: null,
header_icon_isCustom_3: null,
header_info_category_3: null,
header_info_name_3: null,
header_label_4: null,
header_icon_name_4: null,
header_icon_isCustom_4: null,
header_info_category_4: null,
header_info_name_4: null,
header_label_5: null,
header_icon_name_5: null,
header_icon_isCustom_5: null,
header_info_category_5: null,
header_info_name_5: null,
};
export { NAVLINKS, INITIAL_STATE_DATA };

View File

@ -0,0 +1,143 @@
import createHeadersArray from '../createHeadersArray';
describe('FormModal | utils | createHeadersArray', () => {
it('should return an empty array if no header key is set', () => {
const data = {
actionType: null,
attributeName: null,
attributeType: null,
dynamicZoneTarget: null,
forTarget: null,
modalType: null,
pathToSchema: [],
settingType: null,
step: null,
targetUid: null,
headerId: null,
header_label_1: null,
header_icon_name_1: null,
header_icon_isCustom_1: null,
header_info_category_1: null,
header_info_name_1: null,
header_label_2: null,
header_icon_name_2: null,
header_icon_isCustom_2: null,
header_info_category_2: null,
header_info_name_2: null,
header_label_3: null,
header_icon_name_3: null,
header_icon_isCustom_3: null,
header_info_category_3: null,
header_info_name_3: null,
};
expect(createHeadersArray(data)).toEqual([]);
});
it('should return an array containing a header object', () => {
const data = {
actionType: 'something',
attributeName: null,
attributeType: null,
dynamicZoneTarget: null,
forTarget: null,
modalType: null,
pathToSchema: [],
settingType: null,
step: null,
targetUid: null,
headerId: null,
header_label_1: 'restaurant',
header_icon_name_1: 'contentType',
header_icon_isCustom_1: 'false',
header_info_category_1: null,
header_info_name_1: null,
header_label_2: null,
header_icon_name_2: null,
header_icon_isCustom_2: null,
header_info_category_2: null,
header_info_name_2: null,
header_label_3: null,
header_icon_name_3: null,
header_icon_isCustom_3: null,
header_info_category_3: null,
header_info_name_3: null,
};
const expected = [
{
label: 'restaurant',
icon: {
name: 'contentType',
isCustom: false,
},
info: {
name: null,
category: null,
},
},
];
expect(createHeadersArray(data)).toEqual(expected);
});
it('should handle multiple headers correctly', () => {
const data = {
actionType: 'something',
attributeName: null,
attributeType: null,
dynamicZoneTarget: null,
forTarget: null,
modalType: null,
pathToSchema: [],
settingType: null,
step: null,
targetUid: null,
headerId: null,
header_label_1: 'restaurant',
header_icon_name_1: 'bool',
header_icon_isCustom_1: 'true',
header_info_category_1: null,
header_info_name_1: null,
header_label_2: 'closing period',
header_icon_name_2: null,
header_icon_isCustom_2: null,
header_info_category_2: 'default',
header_info_name_2: 'closingperiod',
header_label_3: null,
header_icon_name_3: null,
header_icon_isCustom_3: null,
header_info_category_3: null,
header_info_name_3: null,
};
const expected = [
{
label: 'restaurant',
icon: {
name: 'bool',
isCustom: true,
},
info: {
name: null,
category: null,
},
},
{
label: 'closing period',
icon: {
name: null,
isCustom: false,
},
info: {
name: 'closingperiod',
category: 'default',
},
},
];
expect(createHeadersArray(data)).toEqual(expected);
});
});

View File

@ -0,0 +1,45 @@
import createHeadersObjectFromArray from '../createHeadersObjectFromArray';
describe('FormModal | utils | createHeadersArray', () => {
it('should return a header object', () => {
const data = [
{
label: 'test',
icon: {
name: 'contentType',
isCustom: false,
},
info: {
name: null,
category: null,
},
},
{
label: 'test2',
icon: {
name: 'book',
isCustom: true,
},
info: {
name: 'something',
category: 'default',
},
},
];
const expected = {
header_label_1: 'test',
header_icon_name_1: 'contentType',
header_icon_isCustom_1: false,
header_info_name_1: null,
header_info_category_1: null,
header_label_2: 'test2',
header_icon_name_2: 'book',
header_icon_isCustom_2: true,
header_info_name_2: 'something',
header_info_category_2: 'default',
};
expect(createHeadersObjectFromArray(data)).toEqual(expected);
});
});

View File

@ -43,10 +43,19 @@ function LeftMenu({ wait }) {
actionType: 'edit',
modalType: 'editCategory',
categoryName: data.name,
headerDisplayName: data.name,
headerDisplayCategory: formatMessage({
header_label_1: formatMessage({
id: getTrad('modalForm.header.categories'),
}),
header_icon_name_1: 'component',
header_icon_isCustom_1: false,
header_info_category_1: null,
header_info_name_1: null,
header_label_2: data.name,
header_icon_name_2: null,
header_icon_isCustom_2: false,
header_info_category_2: null,
header_info_name_2: null,
settingType: 'base',
});
@ -86,7 +95,9 @@ function LeftMenu({ wait }) {
await wait();
push({
search: `modalType=${type}&actionType=create&settingType=base&forTarget=${type}`,
search: `modalType=${type}&actionType=create&settingType=base&forTarget=${type}&headerId=${getTrad(
`modalForm.${type}.header-create`
)}&header_icon_name_1=${type}&header_icon_isCustom_1=false&header_label_1=null`,
});
} else {
displayNotificationCTNotSaved();

View File

@ -11,6 +11,7 @@ import {
import { Header } from '@buffetjs/custom';
import ListViewContext from '../../contexts/ListViewContext';
import convertAttrObjToArray from '../../utils/convertAttrObjToArray';
import getAttributeDisplayedType from '../../utils/getAttributeDisplayedType';
import getTrad from '../../utils/getTrad';
import makeSearch from '../../utils/makeSearch';
import ListRow from '../../components/ListRow';
@ -71,41 +72,49 @@ const ListView = () => {
const hasModelBeenModified = !isEqual(modifiedData, initialData);
const forTarget = isInContentTypeView ? 'contentType' : 'component';
const handleClickOpenModalAddField = async (
const handleClickAddField = async (
forTarget,
targetUid,
headerDisplayName,
headerDisplayCategory = null,
headerDisplaySubCategory = null,
subTargetUid = null
firstHeaderObj,
secondHeaderObj,
thirdHeaderObj,
fourthHeaderObj
) => {
// disable the prompt
await wait();
const searchObj = {
modalType: 'chooseAttribute',
forTarget,
targetUid,
headerDisplayName,
headerDisplayCategory,
headerDisplaySubCategory,
subTargetUid,
...firstHeaderObj,
...secondHeaderObj,
...thirdHeaderObj,
...fourthHeaderObj,
};
// Disable the prompt
await wait();
push({ search: makeSearch(searchObj, true) });
push({ search: makeSearch(searchObj) });
};
const handleClickAddComponentToDZ = async dzName => {
const firstHeaderObject = {
header_label_1: currentDataName,
header_icon_name_1: 'dynamiczone',
header_icon_isCustom_1: false,
};
const secondHeaderObj = {
header_label_2: dzName,
};
const search = {
modalType: 'addComponentToDynamicZone',
forTarget: 'contentType',
targetUid,
headerDisplayCategory: currentDataName,
dynamicZoneTarget: dzName,
settingType: 'base',
step: '1',
actionType: 'edit',
headerDisplayName: dzName,
...firstHeaderObject,
...secondHeaderObj,
};
await wait();
@ -118,30 +127,13 @@ const ListView = () => {
targetUid,
attributeName,
type,
headerDisplayName,
headerDisplayCategory = null,
headerDisplaySubCategory = null,
subTargetUid = null
firstHeaderObj,
secondHeaderObj,
thirdHeaderObj,
fourthHeaderObj,
fifthHeaderObj
) => {
let attributeType;
switch (type) {
case 'integer':
case 'biginteger':
case 'decimal':
case 'float':
attributeType = 'number';
break;
case 'string':
case 'text':
attributeType = 'text';
break;
case '':
attributeType = 'relation';
break;
default:
attributeType = type;
}
const attributeType = getAttributeDisplayedType(type);
await wait();
@ -153,11 +145,12 @@ const ListView = () => {
targetUid,
attributeName,
attributeType,
headerDisplayName,
headerDisplayCategory,
headerDisplaySubCategory,
step: type === 'component' ? '2' : null,
subTargetUid,
...firstHeaderObj,
...secondHeaderObj,
...thirdHeaderObj,
...fourthHeaderObj,
...fifthHeaderObj,
};
await wait();
@ -181,6 +174,7 @@ const ListView = () => {
const wait = async () => {
togglePrompt(false);
return new Promise(resolve => setTimeout(resolve, 100));
};
const label = get(modifiedData, [firstMainDataPath, 'schema', 'name'], '');
@ -231,7 +225,10 @@ const ListView = () => {
settingType: 'base',
forTarget: firstMainDataPath,
targetUid,
headerDisplayName: label,
header_label_1: label,
header_icon_isCustom_1: false,
header_icon_name_1: firstMainDataPath,
headerId: getTrad('modalForm.header-edit'),
}),
});
},
@ -258,7 +255,12 @@ const ListView = () => {
color: 'primary',
label: formatMessage({ id: `${pluginId}.button.attributes.add.another` }),
onClick: () => {
handleClickOpenModalAddField(forTarget, targetUid, currentDataName);
const headerDisplayObject = {
header_label_1: currentDataName,
header_icon_name_1: forTarget,
header_icon_isCustom_1: false,
};
handleClickAddField(forTarget, targetUid, headerDisplayObject);
},
};
const goToCMSettingsPage = () => {
@ -281,8 +283,6 @@ const ListView = () => {
? [{ ...configureButtonProps }, { ...addButtonProps }]
: [configureButtonProps];
const handleClickOnTrashIcon = () => {};
const CustomRow = props => {
const { name } = props;
@ -292,7 +292,6 @@ const ListView = () => {
attributeName={name}
name={name}
onClick={handleClickEditField}
onClickDelete={handleClickOnTrashIcon}
/>
);
};
@ -307,7 +306,7 @@ const ListView = () => {
return (
<ListViewContext.Provider
value={{ openModalAddField: handleClickOpenModalAddField }}
value={{ openModalAddField: handleClickAddField }}
>
<Wrapper>
<BackHeader onClick={goBack} />

View File

@ -0,0 +1,154 @@
{
"attribute.boolean": "Boolean",
"attribute.boolean.description": "Yes/no, 1/0, true/false",
"attribute.component": "Komponent",
"attribute.component.description": "Skupina polí, které je možné opakovaně používat",
"attribute.date": "Datum a čas",
"attribute.date.description": "Dialog pro výběr datumu a času",
"attribute.datetime": "Datum a čas",
"attribute.dynamiczone": "Dynamická zóna",
"attribute.dynamiczone.description": "Umožňuje dynamicky zvolit komponenty při úpravách obsahu",
"attribute.email": "E-mailová adresa",
"attribute.email.description": "Pole s automatickou validací formátu e-mailové adresy",
"attribute.enumeration": "Výčet",
"attribute.enumeration.description": "Seznam hodnot s výběrem jedné možnosti",
"attribute.json": "JSON",
"attribute.json.description": "Data vo formátu JSON",
"attribute.media": "Soubory",
"attribute.media.description": "Např. obrázky, videa, ...",
"attribute.null": " ",
"attribute.number": "Číslo",
"attribute.number.description": "Čísla (celé, desetinné)",
"attribute.password": "Heslo",
"attribute.password.description": "Políčko pro zadání hesla",
"attribute.relation": "Relace",
"attribute.relation.description": "Určuje vztah k jinému Typu obsahu",
"attribute.richtext": "Textový editor",
"attribute.richtext.description": "Textové pole s možnostmi formátování",
"attribute.text": "Text",
"attribute.text.description": "Krátký nebo delší text",
"attribute.time": "Čas",
"attribute.uid": "Uuid",
"attribute.uid.description": "Unikátní identifikátor",
"button.attributes.add.another": "Přidat další pole",
"button.component.add": "Přidat komponent",
"button.component.create": "Vytvorit nový komponent",
"button.model.create": "Vytvořit nový Typ obsahu",
"components.componentSelect.no-component-available": "Už jste přidali všechny komponenty",
"components.componentSelect.no-component-available.with-search": "Nenašel se žádný komponent splňující výraz",
"components.componentSelect.value-component": "Označené komponenty: {number} (zadejte hledaný text)",
"components.componentSelect.value-components": "Označené komponenty: {number}",
"component.repeatable": "(opakující)",
"configurations": "nastavení",
"contentType.UID.description": "UID se používa pro vygenerovanie API URL adres a databázových tabulek",
"contentType.collectionName.description": "Vhodné když se názvy Typu obsahu a tabulky liší",
"contentType.collectionName.label": "Interní název",
"contentType.displayName.label": "Název",
"error.contentTypeName.reserved-name": "Tento název je vyhrazený a nemůže být použit",
"error.validation.minSupMax": "Nemůže být nadřazený",
"form.attribute.component.option.add": "Přidat komponent",
"form.attribute.component.option.create": "Vytvořit nový komponent",
"form.attribute.component.option.create.description": "Komponent je dostupný mezi všemi typy a komponenty.",
"form.attribute.component.option.repeatable": "Znovu použitelný komponent",
"form.attribute.component.option.repeatable.description": "Nejlepší pro několikanásobné instance (pole) hodnot, meta tagy, apod...",
"form.attribute.component.option.reuse-existing": "Použít existující komponent",
"form.attribute.component.option.reuse-existing.description": "Používejte už vytvořené komponenty pro uchování konzistentních dat mezi Typy obsahu.",
"form.attribute.component.option.single": "Jednoduchý komponent",
"form.attribute.component.option.single.description": "Vhodné pro seskupení políček, např. celá adresa",
"form.attribute.item.customColumnName": "Vlastné názvy stĺpcov",
"form.attribute.item.customColumnName.description": "Umožňuje přejmenovat databázový sloupec pro potřeby API",
"form.attribute.item.date.type.date": "datum",
"form.attribute.item.date.type.datetime": "datum a čas",
"form.attribute.item.date.type.time": "čas",
"form.attribute.item.defineRelation.fieldName": "Název pole",
"form.attribute.item.enumeration.graphql": "Název pole pro GraphQL",
"form.attribute.item.enumeration.graphql.description": "Umožňuje přepsat přednastavené názvy názvy pro GraphQL",
"form.attribute.item.enumeration.placeholder": "Např.:\nráno\nden\nvečer",
"form.attribute.item.enumeration.rules": "Hodnoty (jedna na řádek)",
"form.attribute.item.maximum": "Maximální hodnota",
"form.attribute.item.maximumLength": "Maximální délka",
"form.attribute.item.minimum": "Minimální hodnota",
"form.attribute.item.minimumLength": "Minimální délka",
"form.attribute.item.number.type": "Číselný formát",
"form.attribute.item.number.type.biginteger": "velké číslo (např.: 123456789)",
"form.attribute.item.number.type.decimal": "desetinné číslo (např.: 2.22)",
"form.attribute.item.number.type.float": "desetinné číslo (např.: 3.33333333)",
"form.attribute.item.number.type.integer": "celé číslo (např.: 10)",
"form.attribute.item.requiredField": "Povinné pole",
"form.attribute.item.requiredField.description": "Nedovolí vytvořit záznam když toto pole zůstane prázdne",
"form.attribute.item.settings.name": "Nastavení",
"form.attribute.item.privateField": "Skryté pole",
"form.attribute.item.privateField.description": "Toto pole se nebude zobrazovat v API",
"form.attribute.item.uniqueField": "Unikátní pole",
"form.attribute.item.uniqueField.description": "Nedovolí vytvořit záznam když už existuje jiný záznam se stejnou hodnotou",
"form.attribute.media.option.multiple": "Více souborů",
"form.attribute.media.option.multiple.description": "Vhodné pro galerii, seznam souborů na stáhnutí",
"form.attribute.media.option.single": "Jeden soubor",
"form.attribute.media.option.single.description": "Vhodné pro profilovou fotku nebo hlavní obrázek",
"form.attribute.settings.default": "Předvolená hodnota",
"form.attribute.text.option.long-text": "Dlouhý text",
"form.attribute.text.option.long-text.description": "Vhodné pro delší popisy. Přesné vyhledávání je vypnuté.",
"form.attribute.text.option.short-text": "Krátky text",
"form.attribute.text.option.short-text.description": "Vhodné pro nadpisy, názvy, URL adresy. Přesné vyhledávání je zapnuté.",
"form.button.add-components-to-dynamiczone": "Přidat komponenty do zóny",
"form.button.add-field": "Přidat další pole",
"form.button.add-first-field-to-created-component": "Přidat první pole do komponentu",
"form.button.add.field.to.component": "Přidat další pole do komponentu",
"form.button.add.field.to.contentType": "Přidat další pole do Typu obsahu",
"form.button.cancel": "Zrušit",
"form.button.configure-component": "Nastavit komponent",
"form.button.configure-view": "Upravit vzhled",
"form.button.continue": "Pokračovat",
"form.button.delete": "Odstránit",
"form.button.finish": "Dokončit",
"form.button.save": "Uložit",
"form.button.select-component": "Vybrat komponent",
"from": "od",
"injected-components.content-manager.edit-settings-view.link.content-types": "Upravit Typ obsahu",
"injected-components.content-manager.edit-settings-view.link.components": "Upravit komponent",
"menu.section.components.name.plural": "Komponenty",
"menu.section.components.name.singular": "Komponent",
"menu.section.models.name.plural": "Typy obsahu",
"menu.section.models.name.singular": "Typ obsahu",
"modalForm.attribute.form.base.name": "Jméno",
"modalForm.attribute.form.base.name.description": "Mezery v názvu pole nejsou povoleny",
"modalForm.attribute.text.type-selection": "Typ",
"modalForm.attributes.select-component": "Vyberte komponent",
"modalForm.attributes.select-components": "Vyberte komponenty",
"modalForm.component.header-create": "Vytvorte komponent",
"modalForm.components.create-component.category.label": "Vyberte kategorii nebo zadejte název pro vytvoření nové",
"modalForm.components.icon.label": "Ikona",
"modalForm.contentType.header-create": "Vytvořit Typ obsahu",
"modalForm.editCategory.base.name.description": "Mezery v názvu kategorie nejsou povoleny",
"modalForm.header-edit": "Upravit {name}",
"modalForm.header.categories": "Kategorie",
"modalForm.sub-header.addComponentToDynamicZone": "Přidat nový komponent do dynamické zóny",
"modalForm.sub-header.attribute.create": "Přidat nové pole {type}",
"modalForm.sub-header.attribute.create.step": "Přidat nový komponent ({step}/2)",
"modalForm.sub-header.attribute.edit": "Upravit {name}",
"modalForm.sub-header.chooseAttribute.component": "Vyberte typ pole pro komponent",
"modalForm.sub-header.chooseAttribute.contentType": "Vyberte typ pole pro Typ obsahu",
"modelPage.attribute.relationWith": "Propojení s",
"modelPage.contentHeader.emptyDescription.description": "žádný popis",
"notification.info.creating.notSaved": "Uložte změny před vytvořením nového Typu obsahu nebo komponentu",
"plugin.description.long": "Navrhněte strukturu webu jednoduše. Vytvořte nová pole a propojení během pár sekund. Soubory se automaticky vytvoří a upraví v rámci projektu.",
"plugin.description.short": "Navrhněte strukturu webu jednoduše.",
"popUpForm.navContainer.advanced": "Pokročilá nastavení",
"popUpForm.navContainer.base": "Základní nastavení",
"popUpWarning.bodyMessage.cancel-modifications": "Jste si jisti, že chcete zrušit úpravy?",
"popUpWarning.bodyMessage.cancel-modifications.with-components": "Jste si jisti, že chcete zrušit úpravy? Některé komponenty byly vytvořeny nebo upraveny...",
"popUpWarning.bodyMessage.category.delete": "Jste si jisti, že chcete odstranit tuto kategorii? Všechny komponentu budou rovněž odstraněny.",
"popUpWarning.bodyMessage.component.delete": "Jste si jisti, že chcete odstranit tento komponent?",
"popUpWarning.bodyMessage.contentType.delete": "Jste si jisti, že chcete odstranit tento Typ obsahu?",
"prompt.unsaved": "Jste si jisti, že chcete odejít? Všechny úpravy budou ztraceny.",
"relation.attributeName.placeholder": "Např: autor, kategorie, tag",
"relation.manyToMany": "má víc a patří všem",
"relation.manyToOne": "má víc",
"relation.manyWay": "má víc",
"relation.oneToMany": "patří více",
"relation.oneToOne": "má jeden a patří jednomu",
"relation.oneWay": "má jeden",
"table.attributes.title.plural": "Pole: {number}",
"table.attributes.title.singular": "Pole: {number}"
}

View File

@ -1,4 +1,5 @@
import ar from './ar.json';
import cs from './cs.json';
import de from './de.json';
import en from './en.json';
import es from './es.json';
@ -18,6 +19,7 @@ import sk from './sk.json';
const trads = {
ar,
cs,
de,
en,
es,

View File

@ -0,0 +1,30 @@
const getAttributeDisplayedType = type => {
let displayedType;
switch (type) {
case 'date':
case 'datetime':
case 'time':
displayedType = 'date';
break;
case 'integer':
case 'biginteger':
case 'decimal':
case 'float':
displayedType = 'number';
break;
case 'string':
case 'text':
displayedType = 'text';
break;
case '':
displayedType = 'relation';
break;
default:
displayedType = type;
}
return displayedType;
};
export default getAttributeDisplayedType;

View File

@ -1,7 +1,6 @@
'use strict';
const validateComponentCategory = require('./validation/component-category');
const componentCategoryService = require('../services/ComponentCategories');
module.exports = {
async editCategory(ctx) {
@ -17,17 +16,24 @@ module.exports = {
strapi.reload.isWatching = false;
const componentCategoryService =
strapi.plugins['content-type-builder'].services.componentcategories;
const newName = await componentCategoryService.editCategory(name, body);
setImmediate(() => strapi.reload());
ctx.send({ name: newName });
},
async deleteCategory(ctx) {
const { name } = ctx.params;
strapi.reload.isWatching = false;
const componentCategoryService =
strapi.plugins['content-type-builder'].services.componentcategories;
await componentCategoryService.deleteCategory(name);
setImmediate(() => strapi.reload());

View File

@ -6,7 +6,6 @@ const {
validateComponentInput,
validateUpdateComponentInput,
} = require('./validation/component');
const componentService = require('../services/Components');
/**
* Components controller
@ -19,6 +18,9 @@ module.exports = {
* @param {Object} ctx - koa context
*/
async getComponents(ctx) {
const componentService =
strapi.plugins['content-type-builder'].services.components;
const data = Object.keys(strapi.components).map(uid => {
return componentService.formatComponent(strapi.components[uid]);
});
@ -40,6 +42,9 @@ module.exports = {
return ctx.send({ error: 'component.notFound' }, 404);
}
const componentService =
strapi.plugins['content-type-builder'].services.components;
ctx.send({ data: componentService.formatComponent(component) });
},
@ -60,6 +65,9 @@ module.exports = {
try {
strapi.reload.isWatching = false;
const componentService =
strapi.plugins['content-type-builder'].services.components;
const component = await componentService.createComponent({
component: body.component,
components: body.components,
@ -96,6 +104,9 @@ module.exports = {
try {
strapi.reload.isWatching = false;
const componentService =
strapi.plugins['content-type-builder'].services.components;
const component = await componentService.editComponent(uid, {
component: body.component,
components: body.components,
@ -125,6 +136,9 @@ module.exports = {
try {
strapi.reload.isWatching = false;
const componentService =
strapi.plugins['content-type-builder'].services.components;
const component = await componentService.deleteComponent(uid);
setImmediate(() => strapi.reload());

View File

@ -7,10 +7,11 @@ const {
validateUpdateContentTypeInput,
} = require('./validation/content-type');
const contentTypeService = require('../services/ContentTypes');
module.exports = {
getContentTypes(ctx) {
const contentTypeService =
strapi.plugins['content-type-builder'].services.contenttypes;
const contentTypes = Object.keys(strapi.contentTypes)
.filter(uid => {
if (uid.startsWith('strapi::')) return false;
@ -36,6 +37,9 @@ module.exports = {
return ctx.send({ error: 'contentType.notFound' }, 404);
}
const contentTypeService =
strapi.plugins['content-type-builder'].services.contenttypes;
ctx.send({ data: contentTypeService.formatContentType(contentType) });
},
@ -51,6 +55,9 @@ module.exports = {
try {
strapi.reload.isWatching = false;
const contentTypeService =
strapi.plugins['content-type-builder'].services.contenttypes;
const component = await contentTypeService.createContentType({
contentType: body.contentType,
components: body.components,
@ -89,6 +96,9 @@ module.exports = {
try {
strapi.reload.isWatching = false;
const contentTypeService =
strapi.plugins['content-type-builder'].services.contenttypes;
const component = await contentTypeService.editContentType(uid, {
contentType: body.contentType,
components: body.components,
@ -113,6 +123,9 @@ module.exports = {
try {
strapi.reload.isWatching = false;
const contentTypeService =
strapi.plugins['content-type-builder'].services.contenttypes;
const component = await contentTypeService.deleteContentType(uid);
setImmediate(() => strapi.reload());

View File

@ -127,6 +127,8 @@ const getTypeShape = (attribute, { modelType } = {}) => {
max: yup.number(),
};
}
case 'time':
case 'datetime':
case 'date': {
return {
default: yup.string(),

View File

@ -31,6 +31,8 @@ module.exports = function createComponentBuilder() {
const targetCT = this.contentTypes.get(uid);
const targetAttribute = targetCT.getAttribute(attribute.via);
if (!targetAttribute) return;
// do not delete polymorphic relations
if (targetAttribute.collection === '*' || targetAttribute.model === '*') {
return;

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -19,13 +19,15 @@
"ListHeader.type": "Typ",
"ListHeader.updated": "Aktualizováno",
"PluginInputFile.link": "prohlédnout",
"PluginInputFile.loading": "Vaše soubory jsou právě nahrávány...",
"PluginInputFile.text": "Přesuňte zde kurzorem vaše soubory do nebo {link} ze souboru k nahrání",
"PluginInputFile.loading": "Soubory se nahrávají...",
"PluginInputFile.text": "Přesuňte zde soubory kurzorem nebo {link} soubory v počítači",
"Upload.status.empty": "Soubory jsou prázdné",
"Upload.status.disabled": "Nahrávání souborů je zakázáno",
"Upload.status.sizeLimit": "{file} soubor je větší než maximální povolená velikost!",
"notification.config.success": "Nastavení bylo uloženo",
"notification.delete.success": "Soubor byl odstraněn",
"notification.dropFile.success": "Váš soubor byl nahrán",
"notification.dropFiles.success": "{number} soubory byly nahrány"
"notification.dropFiles.success": "soubory byly nahrány ({number})",
"plugin.description.long": "Správce multimediálních souborů.",
"plugin.description.short": "Správce multimediálních souborů."
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -9,7 +9,7 @@
"EditForm.inputSelect.label.role": "Výchozí role pro autentifikovaného uživatele",
"EditForm.inputSelect.subscriptions.description": "Limitovat počet odběrů na IP adresu na hodinu.",
"EditForm.inputSelect.subscriptions.label": "Spravovat kvóty na odběry",
"EditForm.inputToggle.description.email": "Zabránit uživateli vytvářet různé účty se stejným meailem a jinými poskytovateli autentifikace.",
"EditForm.inputToggle.description.email": "Zabránit uživateli vytvářet různé účty se stejným e-mailem a jinými poskytovateli autentifikace.",
"EditForm.inputToggle.description.email-confirmation": "Pokud je tato funkce povolena (ON), nově registrovaní uživatelé dostanou potvrzující e-mail.",
"EditForm.inputToggle.description.email-confirmation-redirection": "Po potvrzení e-mailu, zvolte kam budete přesměrováni.",
"EditForm.inputToggle.description.email-reset-password": "Adresa stránky obnovení hesla vaší aplikace",
@ -37,7 +37,7 @@
"Email.template.reset_password": "Obnovit heslo",
"Email.template.success_register": "Registrace proběhla úspěšně",
"HeaderNav.link.advancedSettings": "Pokročilá nastavení",
"HeaderNav.link.emailTemplates": "E-mail templates",
"HeaderNav.link.emailTemplates": "E-mailové šablony",
"HeaderNav.link.providers": "Poskytovatelé",
"HeaderNav.link.roles": "Role & Práva",
"HomePage.header.description": "Definujte role a práva vašim uživatelům.",
@ -54,7 +54,7 @@
"List.title.roles.plural": "{number} role jsou k dispozici",
"List.title.roles.singular": "{number} role je k dispozici",
"Plugin.permissions.application.description": "Definujte všechy povolené akce na vašem projektu.",
"Plugin.permissions.plugins.description": "Definovat všechny akce pro zásuvný modul {name}.",
"Plugin.permissions.plugins.description": "Nastavit všechny akce pro zásuvný modul {name}.",
"Plugins.header.description": "Pouze akce spojené s adresou jsou vypsány níže.",
"Plugins.header.title": "Povolení",
"Policies.InputSelect.empty": "Žádné",
@ -78,21 +78,22 @@
"PopUpForm.Email.validation_email.options.message.placeholder": "<p>Prosím klikněte na tento odkaz pro ověření vašeho účtu</p>",
"PopUpForm.Email.validation_email.options.object.placeholder": "Prosím potvrďte e-mailovou adresu pr %APP_NAME%",
"PopUpForm.Providers.callback.placeholder": "TEXT",
"PopUpForm.Providers.discord.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší Discord konfigurace",
"PopUpForm.Providers.discord.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace Discord",
"PopUpForm.Providers.enabled.description": "If disabled, users won't be able to use this provider.",
"PopUpForm.Providers.enabled.label": "Povolit",
"PopUpForm.Providers.facebook.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší Facebook konfigurace",
"PopUpForm.Providers.github.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší GitHub konfigurace",
"PopUpForm.Providers.google.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší Google konfigurace",
"PopUpForm.Providers.instagram.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší Instagram konfigurace",
"PopUpForm.Providers.facebook.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace Facebook",
"PopUpForm.Providers.github.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace GitHub",
"PopUpForm.Providers.google.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace Google",
"PopUpForm.Providers.instagram.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace Instagram",
"PopUpForm.Providers.vk.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace VK",
"PopUpForm.Providers.key.label": "Client ID",
"PopUpForm.Providers.key.placeholder": "TEXT",
"PopUpForm.Providers.linkedin2.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší Linkedin konfigurace",
"PopUpForm.Providers.microsoft.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší Microsoft konfigurace",
"PopUpForm.Providers.linkedin2.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace Linkedin",
"PopUpForm.Providers.microsoft.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace Microsoft",
"PopUpForm.Providers.redirectURL.front-end.label": "Adresa pro přesměrování na vaši front-end aplikaci",
"PopUpForm.Providers.secret.label": "Client Secret",
"PopUpForm.Providers.secret.placeholder": "TEXT",
"PopUpForm.Providers.twitter.providerConfig.redirectURL": "Adresa pro přesměrování k přidání do vaší Twitter konfigurace",
"PopUpForm.Providers.twitter.providerConfig.redirectURL": "Adresa pro přesměrování, kterou přidejte do nastavení aplikace Twitter",
"PopUpForm.button.cancel": "Zrušit",
"PopUpForm.button.save": "Uložit",
"PopUpForm.header.add.providers": "Přidat nového poskytovatele",

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
Copyright (c) 2017, Vandium Software Inc.
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:

View File

@ -1,4 +1,4 @@
Copyright (c) 2015-2019 Strapi Solutions.
Copyright (c) 2015-2020 Strapi Solutions.
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: