From 0bb290492218e710e113cadccc49cdc475328db4 Mon Sep 17 00:00:00 2001 From: Dominic Cleal Date: Tue, 24 Dec 2024 08:50:08 +0000 Subject: [PATCH 1/6] fix(object-widget): highlight nested validation errors (#7330) When building nested controls for fields in an object widget, ensure the object's ID is passed through as a 'parent' so that errors in nested fields can be linked to the parent. This ensures that a validation error on a field within an object will "bubble up" and show an error on a collapsed object's control in the editor, helping users to identify where there are validation errors. --- packages/decap-cms-widget-object/src/ObjectControl.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/decap-cms-widget-object/src/ObjectControl.js b/packages/decap-cms-widget-object/src/ObjectControl.js index 9a74e1b8290b..2f974aabdcc9 100644 --- a/packages/decap-cms-widget-object/src/ObjectControl.js +++ b/packages/decap-cms-widget-object/src/ObjectControl.js @@ -89,6 +89,7 @@ export default class ObjectControl extends React.Component { isFieldHidden, locale, collapsed, + forID, } = this.props; if (field.get('widget') === 'hidden') { @@ -112,7 +113,7 @@ export default class ObjectControl extends React.Component { onValidate={onValidateObject} processControlRef={controlRef && controlRef.bind(this)} controlRef={controlRef} - parentIds={parentIds} + parentIds={[...parentIds, forID]} isDisabled={isDuplicate} isHidden={isHidden} isFieldDuplicate={isFieldDuplicate} From 76f4b7411684943c515a77932fcac743adce8733 Mon Sep 17 00:00:00 2001 From: Michal Date: Tue, 24 Dec 2024 11:10:40 +0100 Subject: [PATCH 2/6] feat(locale/cs): improve the Czech translations, add new strings from `en` (#7326) Co-authored-by: Martin Jagodic --- packages/decap-cms-locales/src/cs/index.js | 97 +++++++++++++--------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/packages/decap-cms-locales/src/cs/index.js b/packages/decap-cms-locales/src/cs/index.js index 25accc2fa61e..c0075eebe494 100644 --- a/packages/decap-cms-locales/src/cs/index.js +++ b/packages/decap-cms-locales/src/cs/index.js @@ -19,7 +19,7 @@ const cs = { app: { header: { content: 'Obsah', - workflow: 'Workflow', + workflow: 'Schvalování', media: 'Média', quickAdd: 'Přidat', }, @@ -44,7 +44,7 @@ const cs = { collectionTop: { sortBy: 'Seřadit podle', viewAs: 'Zobrazit jako', - newButton: 'Nový %{collectionLabel}', + newButton: 'Přidat', ascending: 'Vzestupné', descending: 'Sestupné', searchResults: 'Výsledky vyhledávání pro "%{searchTerm}"', @@ -53,10 +53,10 @@ const cs = { groupBy: 'Seskupit podle', }, entries: { - loadingEntries: 'Načítání záznamů', - cachingEntries: 'Úkládání záznamů do mezipaměti', + loadingEntries: 'Načítání položek', + cachingEntries: 'Úkládání položek do mezipaměti', longerLoading: 'Načítání může trvat několik minut', - noEntries: 'Žádné záznamy', + noEntries: 'Žádné položky', }, groups: { other: 'Ostatní', @@ -94,24 +94,27 @@ const cs = { }, i18n: { writingInLocale: 'Psát v %{locale}', + copyFromLocale: 'Vyplnit z jiného jazyka', + copyFromLocaleConfirm: + 'Chcete vyplnit data z jazyka %{locale}?\nVeškerý současný obsah bude přepsán.', }, }, editor: { - onLeavePage: 'Chcete opravdu opustit tuto stránku?', - onUpdatingWithUnsavedChanges: 'Máte neuložené změny. Uložte je prosím před změnou statusu.', + onLeavePage: 'Opravdu chcete opustit tuto stránku?', + onUpdatingWithUnsavedChanges: 'Máte neuložené změny. Před změnou stavu je prosím uložte.', onPublishingNotReady: 'Změňte stav na „Připraveno“ před publikováním.', - onPublishingWithUnsavedChanges: 'Máte neuložené změny, prosím uložte je před publikováním.', - onPublishing: 'Chcete opravdu publikovat tento záznam?', - onUnpublishing: 'Chcete opravdu zrušit publikování tohoto záznamu?', + onPublishingWithUnsavedChanges: 'Máte neuložené změny, před publikováním je prosím uložte.', + onPublishing: 'Opravdu chcete publikovat tuto položku?', + onUnpublishing: 'Opravdu chcete zrušit publikování této položky?', onDeleteWithUnsavedChanges: - 'Chcete opravdu vymazat tento publikovaný záznam a všechny neuložené změny z této relace?', - onDeletePublishedEntry: 'Chcete opravdu smazat tento publikovaný záznam?', + 'Opravdu chcete vymazat tuto publikovanou položku a všechny neuložené změny z této relace?', + onDeletePublishedEntry: 'Opravdu chcete smazat tuto publikovanou položku?', onDeleteUnpublishedChangesWithUnsavedChanges: - 'Tato akce vymaže všechny nepublikované změny v tomto záznamu a také všechny neuložené změny z této relace. Chcete záznam skutečně vymazat?', + 'Tato akce vymaže všechny nepublikované změny v této položce a také všechny neuložené změny z této relace. Chcete položku skutečně vymazat?', onDeleteUnpublishedChanges: - 'Všechny nepublivkoané změny v tomto záznamu budou vymazány. Chcete ho skuteně vymazat?', - loadingEntry: 'Načítání záznamu…', - confirmLoadBackup: 'Lokální kopie tohoto záznamu byla nalezena, chcete ji použít?', + 'Všechny nepublikované změny v této položce budou vymazány. Chcete ji skuteně vymazat?', + loadingEntry: 'Načítání položky…', + confirmLoadBackup: 'Lokální kopie této položky byla nalezena, chcete ji použít?', }, editorInterface: { toggleI18n: 'Přepnout lokalizaci', @@ -128,14 +131,18 @@ const cs = { publishAndCreateNew: 'Publikovat a vytvořit nový', publishAndDuplicate: 'Publikovat a duplikovat', deleteUnpublishedChanges: 'Vymazat nepublikované změny', - deleteUnpublishedEntry: 'Vymazat nepublikovaný záznam', - deletePublishedEntry: 'Vymazat publikovaný záznam', - deleteEntry: 'Vymazat záznam', + deleteUnpublishedEntry: 'Vymazat nepublikovanou položku', + deletePublishedEntry: 'Vymazat publikovanou položku', + deleteEntry: 'Vymazat položku', saving: 'Ukládání…', save: 'Uložit', + statusInfoTooltipDraft: + 'Položka je ve stavu konceptu. Po dokončení úprav ji označte stavem ‚V revizi‘.', + statusInfoTooltipInReview: + 'Položka je označena jako připravená k revizi, žádná další akce není třeba. Můžete ovšem provádět další změny, zatímco probíhá revize.', deleting: 'Vymazávání…', updating: 'Aktualizace…', - status: 'Status: %{status}', + status: 'Stav: %{status}', backCollection: ' Píšete v kolekci %{collectionLabel}', unsavedChanges: 'Neuložené změny', changesSaved: 'Změny uloženy', @@ -164,19 +171,25 @@ const cs = { }, image: { choose: 'Vyberte obrázek', + chooseMultiple: 'Vyberte obrázky', chooseUrl: 'Přidat z URL', replaceUrl: 'Nahradit z URL', promptUrl: 'Zadejte URL obrázku', chooseDifferent: 'Vyberte jiný obrázek', + addMore: 'Přidat další obrázky', remove: 'Odstranit obrázek', + removeAll: 'Odstranit všechny obrázky', }, file: { choose: 'Vyberte soubor', chooseUrl: 'Přidat z URL', + chooseMultiple: 'Vyberte soubory', replaceUrl: 'Nahradit z URL', promptUrl: 'Zadejte URL souboru', chooseDifferent: 'Vyberte jiný soubor', + addMore: 'Přidat další soubory', remove: 'Odebrat soubor', + removeAll: 'Odstranit všechny soubory', }, unknownControl: { noControl: "Žádné ovládání pro widget '%{widget}'.", @@ -196,6 +209,10 @@ const cs = { now: 'Teď', clear: 'Vymazat', }, + list: { + add: 'Přidat', + addType: 'Přidat', + }, }, }, mediaLibrary: { @@ -249,18 +266,18 @@ const cs = { logOut: 'Odhlásit', }, toast: { - onFailToLoadEntries: 'Chyba při načítání záznamu: %{details}', + onFailToLoadEntries: 'Chyba při načítání položky: %{details}', onFailToLoadDeployPreview: 'Chyba při načítání náhledu: %{details}', - onFailToPersist: 'Chyba při ukládání záznamu: %{details}', - onFailToDelete: 'Chyba při vymazávání záznamu: %{details}', - onFailToUpdateStatus: 'Chyba při změně stavu záznamu: %{details}', + onFailToPersist: 'Chyba při ukládání položky: %{details}', + onFailToDelete: 'Chyba při vymazávání položky: %{details}', + onFailToUpdateStatus: 'Chyba při změně stavu položky: %{details}', missingRequiredField: 'Vynechali jste povinné pole. Prosím vyplňte ho.', - entrySaved: 'Záznam uložen', - entryPublished: 'Záznam publikován', - entryUnpublished: 'Publikování záznamu zrušeno', - onFailToPublishEntry: 'Chyba při publikování záznamu: %{details}', - onFailToUnpublishEntry: 'Chyba při rušení publikování záznamu: %{details}', - entryUpdated: 'Stav záznamu byl změněn', + entrySaved: 'Položka uložena', + entryPublished: 'Položka publikována', + entryUnpublished: 'Publikování položky zrušeno', + onFailToPublishEntry: 'Chyba při publikování položky: %{details}', + onFailToUnpublishEntry: 'Chyba při rušení publikování položky: %{details}', + entryUpdated: 'Stav položky byl změněn', onDeleteUnpublishedChanges: 'Nepublikované změny byly smazány', onFailToAuth: '%{details}', onLoggedOut: 'Byli jste odhlášeni, prosím zálohujte všechna data a znova se přihlašte', @@ -269,11 +286,11 @@ const cs = { }, workflow: { workflow: { - loading: 'Načítání workflow záznamů', - workflowHeading: 'Schvalovací Workflow', - newPost: 'Nový post', + loading: 'Načítání položek', + workflowHeading: 'Schvalovací proces', + newPost: 'Nová položka', description: - '%{smart_count} záznam čeká na schválení, %{readyCount} připraven k publikaci. |||| %{smart_count} čeká na schválení, %{readyCount} připraveno k publikaci. ', + '%{smart_count} čeká na schválení, %{readyCount} připraven k publikaci. |||| %{smart_count} čeká na schválení, %{readyCount} připraveno k publikaci. ', dateFormat: 'MMMM D', }, workflowCard: { @@ -281,19 +298,19 @@ const cs = { lastChangeNoAuthor: '%{date}', lastChangeNoDate: '%{author}', deleteChanges: 'Vymazat změny', - deleteNewEntry: 'Vymazat nový záznam', + deleteNewEntry: 'Vymazat novou položku', publishChanges: 'Publikovat změny', - publishNewEntry: 'Publikovat nový záznam', + publishNewEntry: 'Publikovat novou položku', }, workflowList: { - onDeleteEntry: 'Opravdu chcete smazat tento záznam?', + onDeleteEntry: 'Opravdu chcete smazat tuto položku?', onPublishingNotReadyEntry: - 'Pouze položky se statusem "Připraveno" mohou být publikováno. Pro umožnění publikace musíte přetáhnout kartu do sloupce "Připraveno"', - onPublishEntry: 'Opravdu chcete publikovat tento záznam?', + 'Pouze položky se stavem "Připraveno" mohou být publikovány. Pro umožnění publikace musíte přetáhnout kartu do sloupce "Připraveno"', + onPublishEntry: 'Opravdu chcete publikovat tuto položku?', draftHeader: 'Koncepty', inReviewHeader: 'V revizi', readyHeader: 'Připraveno', - currentEntries: '%{smart_count} záznam |||| %{smart_count} záznamů', + currentEntries: '%{smart_count} položka |||| %{smart_count} položek', }, }, }; From b41e43a73218f0950ea9c96f21dfe0a31aa173b3 Mon Sep 17 00:00:00 2001 From: Marko Ilievski Date: Tue, 24 Dec 2024 11:51:14 +0100 Subject: [PATCH 3/6] feat: add mk (Macedonian) locale (#7346) * add Macedonian locale * feat(locale): format and lint for mk locale --------- Co-authored-by: Martin Jagodic --- packages/decap-cms-locales/src/index.js | 1 + packages/decap-cms-locales/src/mk/index.js | 321 +++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 packages/decap-cms-locales/src/mk/index.js diff --git a/packages/decap-cms-locales/src/index.js b/packages/decap-cms-locales/src/index.js index 493163a0c0d0..1b8529cc5efa 100644 --- a/packages/decap-cms-locales/src/index.js +++ b/packages/decap-cms-locales/src/index.js @@ -28,6 +28,7 @@ export { default as ko } from './ko'; export { default as hr } from './hr'; export { default as sl } from './sl'; export { default as bg } from './bg'; +export { default as mk } from './mk'; export { default as zh_Hans } from './zh_Hans'; export { default as he } from './he'; export { default as ua } from './ua'; diff --git a/packages/decap-cms-locales/src/mk/index.js b/packages/decap-cms-locales/src/mk/index.js new file mode 100644 index 000000000000..a7bdd0c8e9b7 --- /dev/null +++ b/packages/decap-cms-locales/src/mk/index.js @@ -0,0 +1,321 @@ +const mk = { + auth: { + login: 'Најава', + loggingIn: 'Се најавува...', + loginWithNetlifyIdentity: 'Најава со Netlify Identity', + loginWithAzure: 'Најава со Azure', + loginWithBitbucket: 'Најава со Bitbucket', + loginWithGitHub: 'Најава со GitHub', + loginWithGitLab: 'Најава со GitLab', + loginWithGitea: 'Најава со Gitea', + errors: { + email: 'Внесете ја вашата е-пошта.', + password: 'Внесете ја вашата лозинка.', + identitySettings: + 'Нема пристап до поставките. Ако користите git-gateway, не заборавајте да ја активирате услугата Identity и Git Gateway.', + }, + }, + app: { + header: { + content: 'Содржина', + workflow: 'Работен процес', + media: 'Медиумски ресурси', + quickAdd: 'Брзо додавање', + }, + app: { + errorHeader: 'Грешка при вчитувањето на CMS конфигурацијата', + configErrors: 'Грешки во конфигурацијата', + checkConfigYml: 'Проверете ја вашата config.yml датотека.', + loadingConfig: 'Се вчитува конфигурацијата...', + waitingBackend: 'Се чека одговор од серверот...', + }, + notFoundPage: { + header: 'Не е пронајдено', + }, + }, + collection: { + sidebar: { + collections: 'Колекции', + allCollections: 'Сите колекции', + searchAll: 'Пребарај секаде', + searchIn: 'Пребарај во', + }, + collectionTop: { + sortBy: 'Подреди според', + viewAs: 'Прикажи како', + newButton: 'Креирај %{collectionLabel}', + ascending: 'Растечки', + descending: 'Опаѓачки', + searchResults: 'Резултати од пребарувањето за "%{searchTerm}"', + searchResultsInCollection: 'Резултати од пребарувањето за "%{searchTerm}" во %{collection}', + filterBy: 'Филтрирај по', + groupBy: 'Групирај по', + }, + entries: { + loadingEntries: 'Се вчитуваат записи...', + cachingEntries: 'Се кешираат записи...', + longerLoading: 'Ова може да потрае неколку минути', + noEntries: 'Нема записи', + }, + groups: { + other: 'Други', + negateLabel: 'Не %{label}', + }, + defaultFields: { + author: { + label: 'Автор', + }, + updatedOn: { + label: 'Ажурирано на', + }, + }, + }, + editor: { + editorControl: { + field: { + optional: 'незадолжително', + }, + }, + editorControlPane: { + widget: { + required: '%{fieldLabel} е задолжително.', + regexPattern: '%{fieldLabel} не се совпаѓа со шаблонот: %{pattern}.', + processing: '%{fieldLabel} се обработува.', + range: '%{fieldLabel} мора да биде помеѓу %{minValue} и %{maxValue}.', + min: '%{fieldLabel} мора да биде најмалку %{minValue}.', + max: '%{fieldLabel} мора да биде %{maxValue} или помалку.', + rangeCount: '%{fieldLabel} мора да има помеѓу %{minCount} и %{maxCount} елементи.', + rangeCountExact: '%{fieldLabel} мора да има точно %{count} елементи.', + rangeMin: '%{fieldLabel} мора да има најмалку %{minCount} елементи.', + rangeMax: '%{fieldLabel} мора да има %{maxCount} или помалку елементи.', + invalidPath: `'%{path}' не е валидна патека`, + pathExists: `Патеката '%{path}' веќе постои`, + }, + i18n: { + writingInLocale: 'Пишување на %{locale}', + copyFromLocale: 'Пополнете од друг јазик', + copyFromLocaleConfirm: + 'Дали сакате да ги пополните податоците од јазикот %{locale}?\nСите постоечки содржини ќе бидат презапишани.', + }, + }, + editor: { + onLeavePage: 'Дали сте сигурни дека сакате да ја напуштите страницата?', + onUpdatingWithUnsavedChanges: + 'Имате незачувани промени, зачувајте пред ажурирање на статусот.', + onPublishingNotReady: 'Ажурирајте го статусот на "Спремно" пред објавување.', + onPublishingWithUnsavedChanges: 'Имате незачувани промени, зачувајте пред објавување.', + onPublishing: 'Дали сте сигурни дека сакате да го објавите овој запис?', + onUnpublishing: 'Дали сте сигурни дека сакате да го откажете објавувањето на овој запис?', + onDeleteWithUnsavedChanges: + 'Дали сте сигурни дека сакате да го избришете овој објавен запис и незачуваните промени од тековната сесија?', + onDeletePublishedEntry: 'Дали сте сигурни дека сакате да го избришете овој објавен запис?', + onDeleteUnpublishedChangesWithUnsavedChanges: + 'Ова ќе ги избрише сите необјавени промени во овој запис и незачуваните промени од тековната сесија. Дали сакате да продолжите?', + onDeleteUnpublishedChanges: + 'Сите необјавени промени ќе бидат избришани. Дали сакате да продолжите?', + loadingEntry: 'Се вчитува запис...', + confirmLoadBackup: + 'Се пронајде локална резервна копија за овој запис. Дали сакате да ја користите?', + }, + editorInterface: { + toggleI18n: 'Вклучи i18n', + togglePreview: 'Вклучи преглед', + toggleScrollSync: 'Синхронизирај скрол', + }, + editorToolbar: { + publishing: 'Се објавува...', + publish: 'Објави', + published: 'Објавен', + unpublish: 'Откажи објавување', + duplicate: 'Дуплирај', + unpublishing: 'Откажување објава...', + publishAndCreateNew: 'Објави и креирај нов', + publishAndDuplicate: 'Објави и дуплирај', + deleteUnpublishedChanges: 'Избриши необјавени промени', + deleteUnpublishedEntry: 'Избриши необјавен запис', + deletePublishedEntry: 'Избриши објавен запис', + deleteEntry: 'Избриши запис', + saving: 'Се зачувува...', + save: 'Зачувај', + statusInfoTooltipDraft: + 'Статусот на записот е поставен како нацрт. За да го финализирате и поднесете за преглед, поставете го статусот "Во рецензија".', + statusInfoTooltipInReview: + 'Записот е во рецензија, не се потребни дополнителни акции. Сепак, можете да направите дополнителни промени додека е во рецензија.', + deleting: 'Се брише...', + updating: 'Се ажурира...', + status: 'Статус: %{status}', + backCollection: ' Запишување во колекцијата %{collectionLabel}', + unsavedChanges: 'Незачувани промени', + changesSaved: 'Промените се зачувани', + draft: 'Нацрт', + inReview: 'Во рецензија', + ready: 'Спремно', + publishNow: 'Објави сега', + deployPreviewPendingButtonLabel: 'Проверете ја верзијата во рецензија', + deployPreviewButtonLabel: 'Прикажи рецензија', + deployButtonLabel: 'Прикажи во живо', + }, + editorWidgets: { + markdown: { + bold: 'Задебелено', + italic: 'Косо', + code: 'Код', + link: 'Линк', + linkPrompt: 'Внесете URL за линкот', + headings: 'Наслови', + quote: 'Цитат', + bulletedList: 'Список со булети', + numberedList: 'Нумериран список', + addComponent: 'Додај компонента', + richText: 'Форматиран текст', + markdown: 'Markdown', + }, + image: { + choose: 'Избери слика', + chooseMultiple: 'Избери повеќе слики', + chooseUrl: 'Вметни од URL', + replaceUrl: 'Замени со URL', + promptUrl: 'Внесете URL за сликата', + chooseDifferent: 'Избери друга слика', + addMore: 'Додај повеќе слики', + remove: 'Отстрани слика', + removeAll: 'Отстрани ги сите слики', + }, + file: { + choose: 'Избери датотека', + chooseUrl: 'Вметни од URL', + chooseMultiple: 'Избери повеќе датотеки', + replaceUrl: 'Замени со URL', + promptUrl: 'Внесете URL за датотеката', + chooseDifferent: 'Избери друга датотека', + addMore: 'Додај повеќе датотеки', + remove: 'Отстрани датотека', + removeAll: 'Отстрани ги сите датотеки', + }, + unknownControl: { + noControl: "Нема контрола за виџетот '%{widget}'.", + }, + unknownPreview: { + noPreview: "Нема преглед за виџетот '%{widget}'.", + }, + headingOptions: { + headingOne: 'Наслов 1', + headingTwo: 'Наслов 2', + headingThree: 'Наслов 3', + headingFour: 'Наслов 4', + headingFive: 'Наслов 5', + headingSix: 'Наслов 6', + }, + datetime: { + now: 'Сега', + clear: 'Исчисти', + }, + list: { + add: 'Додај %{item}', + addType: 'Додај %{item}', + }, + }, + }, + mediaLibrary: { + mediaLibraryCard: { + draft: 'Нацрт', + copy: 'Копирај', + copyUrl: 'Копирај URL', + copyPath: 'Копирај патека', + copyName: 'Копирај име', + copied: 'Копирано', + }, + mediaLibrary: { + onDelete: 'Дали сте сигурни дека сакате да го избришете избраниот медиум?', + fileTooLarge: + 'Датотеката е преголема.\nКонфигурацијата не дозволува датотеки поголеми од %{size} kB.', + }, + mediaLibraryModal: { + loading: 'Се вчитува...', + noResults: 'Нема резултати.', + noAssetsFound: 'Не се пронајдени ресурси.', + noImagesFound: 'Не се пронајдени слики.', + private: 'Приватно', + images: 'Слики', + mediaAssets: 'Медиумски ресурси', + search: 'Пребарај...', + uploading: 'Се прикачува...', + upload: 'Прикачи', + download: 'Преземи', + deleting: 'Се брише...', + deleteSelected: 'Избриши ги селектираните', + chooseSelected: 'Избери од селектираните', + }, + }, + ui: { + default: { + goBackToSite: 'Назад кон сајтот', + }, + errorBoundary: { + title: 'Грешка', + details: 'Се случи грешка - ве молиме ', + reportIt: 'пријавете проблем на GitHub.', + detailsHeading: 'Детали', + privacyWarning: + 'При отворање на тикет, автоматски се пополнува со порака за грешка и податоци за дебагирање.\nПроверете дали податоците се точни и отстранете ги доверливите информации ако ги има.', + recoveredEntry: { + heading: 'Повратен документ', + warning: 'Зачувајте го ова некаде пред да ја напуштите страницата!', + copyButtonLabel: 'Копирај во клипборд', + }, + }, + settingsDropdown: { + logOut: 'Одјави се', + }, + toast: { + onFailToLoadEntries: 'Неуспешно вчитување на запис: %{details}', + onFailToLoadDeployPreview: 'Неуспешно вчитување на преглед: %{details}', + onFailToPersist: 'Неуспешно зачувување на запис: %{details}', + onFailToDelete: 'Неуспешно бришење на запис: %{details}', + onFailToUpdateStatus: 'Неуспешно ажурирање на статусот: %{details}', + missingRequiredField: + 'Пропуштивте задолжително поле. Ве молиме пополнете го пред да зачувате.', + entrySaved: 'Записот е зачуван', + entryPublished: 'Записот е објавен', + entryUnpublished: 'Записот е необјавен', + onFailToPublishEntry: 'Неуспешно објавување: %{details}', + onFailToUnpublishEntry: 'Неуспешно откажување објавување: %{details}', + entryUpdated: 'Статусот на записот е ажуриран', + onDeleteUnpublishedChanges: 'Необјавените промени се избришани', + onFailToAuth: '%{details}', + onLoggedOut: 'Одјавени сте, зачувајте ги податоците и најавете се повторно.', + onBackendDown: 'Серверот има прекин. Видете %{details} за повеќе информации.', + }, + }, + workflow: { + workflow: { + loading: 'Се вчитуваат записи за уредувачкиот работен процес', + workflowHeading: 'Уредувачки работен процес', + newPost: 'Нова објава', + description: + '%{smart_count} запис чека за рецензија, %{readyCount} подготвен за објава. |||| %{smart_count} записи чекаат за рецензија, %{readyCount} подготвени за објава.', + dateFormat: 'MMMM D', + }, + workflowCard: { + lastChange: '%{date} од %{author}', + lastChangeNoAuthor: '%{date}', + lastChangeNoDate: 'од %{author}', + deleteChanges: 'Избриши промени', + deleteNewEntry: 'Избриши нов запис', + publishChanges: 'Објави промени', + publishNewEntry: 'Објави нов запис', + }, + workflowList: { + onDeleteEntry: 'Дали сте сигурни дека сакате да го избришете овој запис?', + onPublishingNotReadyEntry: + 'Само записи со статус "Спремно" можат да се објават. Повлечете ја картичката во колоната "Спремно" за да го овозможите објавувањето.', + onPublishEntry: 'Дали сте сигурни дека сакате да го објавите овој запис?', + draftHeader: 'Нацрти', + inReviewHeader: 'Во рецензија', + readyHeader: 'Спремно', + currentEntries: '%{smart_count} запис |||| %{smart_count} записи', + }, + }, +}; + +export default mk; From 6cd7cb3b41718e20b44acaae1e2ffd73129520c2 Mon Sep 17 00:00:00 2001 From: Felix Gnass Date: Wed, 15 Jan 2025 13:19:57 +0100 Subject: [PATCH 4/6] fix(build): Fix ESM output (#7357) (#7358) * fix: leave modules untouched in ESM build This prevents Babel from converting the ESM module syntax to CommonJS. * fix: include clearing the Nx cache in the clean script Otherwise a "clean" build will still use old artefacts from the cache. * fix: don't build dependant packages in parallel This ensures that any local dependencies are built first. * fix: don't import from dist/esm Thanks to the other changes in this PR, we can now safely import from the decap-cms-app. * fix(ci): use actions/setup-node cache option This will hopefully fix the error we get on Windows during `npm install` * chore(ci): fix typo and output matrix.node-version * ci: run e2e tests in same container * ci: fail fast to reduce build time and save cpu cycles * ci: run on all branches * ci: set IS_FORK=true if the repo owner is not decaporg * ci: use afterEach hook to fail fast * fix(app): add module field to package.json * fix(app): add missing decap-cms-backend-gitea dependency --------- Co-authored-by: Martin Jagodic --- .github/workflows/nodejs.yml | 62 ++++++---------------------- babel.config.js | 2 +- cypress/run.mjs | 42 +++++++++---------- cypress/support/e2e.js | 6 +++ nx.json | 6 ++- package.json | 2 +- packages/decap-cms-app/package.json | 2 + packages/decap-cms/src/extensions.js | 2 +- packages/decap-cms/src/index.js | 2 +- 9 files changed, 49 insertions(+), 77 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index abf00df7e748..15d945ebcfc0 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -2,8 +2,6 @@ name: Node CI on: push: - branches: - - main pull_request: types: [opened, synchronize, reopened] @@ -28,73 +26,37 @@ jobs: matrix: os: [macos-latest, windows-latest, ubuntu-latest] node-version: [18.x, 20.x] + fail-fast: true if: ${{ needs.changes.outputs.cms == 'true' }} steps: - uses: actions/checkout@v3 - - name: Use Node.js {{ matrix.node-version }} + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} check-latest: true + cache: 'npm' - name: log versions run: node --version && npm --version && yarn --version - - name: install dependecies - run: npm install + - name: install dependencies + run: npm ci - name: run unit tests run: npm run test:ci - env: - CI: true - NODE_OPTIONS: --max-old-space-size=4096 - name: build demo site run: npm run build:demo + - name: run e2e tests + if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20.x' + run: npm run test:e2e:run-ci env: - NODE_OPTIONS: --max-old-space-size=4096 - - uses: actions/upload-artifact@master - with: - name: dev-test-website-${{ runner.os }}-${{ matrix.node-version }} - path: dev-test - - e2e-with-cypress: - needs: [changes, build] - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [18.x, 20.x] - fail-fast: false - - if: ${{ needs.changes.outputs.cms == 'true' }} - steps: - - uses: actions/checkout@v3 - - name: Use Node.js - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - check-latest: true - - uses: actions/download-artifact@master - with: - name: dev-test-website-${{ runner.os }}-18.x - path: dev-test - - name: npm install - run: | - node --version - npm --version - yarn --version - npm install - - name: e2e test - run: | - npm run test:e2e:run-ci - env: - IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} + IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true || github.repository_owner != 'decaporg' }} CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} NODE_OPTIONS: --max-old-space-size=4096 - MACHINE_COUNT: 2 - MACHINE_INDEX: ${{ matrix.node-version }} TZ: Europe/Amsterdam - uses: actions/upload-artifact@v3 - if: ${{ always() }} + if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20.x' && failure() with: - name: cypress-results-${{ matrix.node-version }} + name: cypress-results path: | cypress/screenshots cypress/videos + diff --git a/babel.config.js b/babel.config.js index 9b7b21006133..4256c8dfdb2f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -42,7 +42,7 @@ const svgo = { function presets() { return [ '@babel/preset-react', - '@babel/preset-env', + ['@babel/preset-env', isESM ? { modules: false } : {}], [ '@emotion/babel-preset-css-prop', { diff --git a/cypress/run.mjs b/cypress/run.mjs index 9e77c6786d9d..dfc8235840b4 100644 --- a/cypress/run.mjs +++ b/cypress/run.mjs @@ -2,10 +2,17 @@ import execa from 'execa'; import { globby } from 'globby'; async function runCypress() { + const args = ['run', '--browser', 'chrome', '--headless']; + + const specs = await globby(['cypress/e2e/*spec*.js']); + if (specs.length === 0) { + console.log('No test files found in cypress/e2e/*spec*.js'); + process.exit(1); + } + if (process.env.IS_FORK === 'true') { const machineIndex = parseInt(process.env.MACHINE_INDEX); const machineCount = parseInt(process.env.MACHINE_COUNT); - const specs = await globby(['cypress/integration/*spec*.js']); const specsPerMachine = Math.floor(specs.length / machineCount); const start = (machineIndex - 1) * specsPerMachine; const machineSpecs = @@ -13,29 +20,22 @@ async function runCypress() { ? specs.slice(start) : specs.slice(start, start + specsPerMachine); - await execa( - 'cypress', - ['run', '--browser', 'chrome', '--headless', '--spec', machineSpecs.join(',')], - { stdio: 'inherit', preferLocal: true }, - ); + args.push('--spec', machineSpecs.join(',')); } else { - await execa( - 'cypress', - [ - 'run', - '--browser', - 'chrome', - '--headless', - '--record', - '--parallel', - '--ci-build-id', - process.env.GITHUB_SHA, - '--group', - 'GitHub CI', - ], - { stdio: 'inherit', preferLocal: true }, + args.push( + '--record', + '--parallel', + '--ci-build-id', + process.env.GITHUB_SHA, + '--group', + 'GitHub CI', + '--spec', + specs.join(','), ); } + + console.log('Running Cypress with args:', args.join(' ')); + await execa('cypress', args, { stdio: 'inherit', preferLocal: true }); } runCypress(); diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js index 6504113e81ee..80fcca6176c2 100644 --- a/cypress/support/e2e.js +++ b/cypress/support/e2e.js @@ -26,3 +26,9 @@ addMatchImageSnapshotCommand({ Cypress.on('uncaught:exception', () => false); import './commands'; + +afterEach(function () { + if (this.currentTest.state === 'failed') { + Cypress.runner.stop(); + } +}); diff --git a/nx.json b/nx.json index 735930366acb..21efa37fa51c 100644 --- a/nx.json +++ b/nx.json @@ -1,10 +1,12 @@ { "targetDefaults": { "build:esm": { - "cache": true + "cache": true, + "dependsOn": ["^build:esm"] }, "build": { - "cache": true + "cache": true, + "dependsOn": ["^build"] } }, "namedInputs": { diff --git a/package.json b/package.json index 26ba8f24d7c8..4c8a1300b45a 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build-preview": "npm run build && nx run decap-cms:build-preview --output-style=stream", "type-check": "tsc --noEmit", "type-check:watch": "npm run type-check -- --watch", - "clean": "rimraf \"packages/*/dist\" dev-test/dist \"packages/*/node_modules\"", + "clean": "rimraf \"packages/*/dist\" dev-test/dist \"packages/*/node_modules\" \".nx/cache\"", "reset": "npm run clean", "test": "npm run lint && npm run type-check && npm run test:unit", "test:all": "npm run test && npm run test:e2e", diff --git a/packages/decap-cms-app/package.json b/packages/decap-cms-app/package.json index 098cde0872d1..e5ce603dfe54 100644 --- a/packages/decap-cms-app/package.json +++ b/packages/decap-cms-app/package.json @@ -5,6 +5,7 @@ "homepage": "https://www.decapcms.org", "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-app", "bugs": "https://github.com/decaporg/decap-cms/issues", + "module": "dist/esm/index.js", "main": "dist/decap-cms-app.js", "files": [ "src/", @@ -34,6 +35,7 @@ "decap-cms-backend-azure": "^3.1.3", "decap-cms-backend-bitbucket": "^3.1.4", "decap-cms-backend-git-gateway": "^3.2.2", + "decap-cms-backend-gitea": "^3.1.5", "decap-cms-backend-github": "^3.2.2", "decap-cms-backend-gitlab": "^3.2.2", "decap-cms-backend-proxy": "^3.1.4", diff --git a/packages/decap-cms/src/extensions.js b/packages/decap-cms/src/extensions.js index abac8d0b2b2b..9b097dbad095 100644 --- a/packages/decap-cms/src/extensions.js +++ b/packages/decap-cms/src/extensions.js @@ -1,4 +1,4 @@ -import { DecapCmsApp as CMS } from 'decap-cms-app/dist/esm'; +import { DecapCmsApp as CMS } from 'decap-cms-app'; // Media libraries import uploadcare from 'decap-cms-media-library-uploadcare'; import cloudinary from 'decap-cms-media-library-cloudinary'; diff --git a/packages/decap-cms/src/index.js b/packages/decap-cms/src/index.js index bef5752e08a5..fc0c632efc2e 100644 --- a/packages/decap-cms/src/index.js +++ b/packages/decap-cms/src/index.js @@ -1,6 +1,6 @@ import createReactClass from 'create-react-class'; import React from 'react'; -import { DecapCmsApp as CMS } from 'decap-cms-app/dist/esm'; +import { DecapCmsApp as CMS } from 'decap-cms-app'; import './extensions'; /** From 2ffe3f85eb224d25a8765a2365caabacc0ae87ae Mon Sep 17 00:00:00 2001 From: Anze Demsar Date: Wed, 15 Jan 2025 13:33:16 +0100 Subject: [PATCH 5/6] chore(release): publish - decap-cms@3.5.0 - decap-cms-app@3.5.0 - decap-cms-locales@3.3.0 - decap-cms-widget-object@3.2.0 --- package-lock.json | 15 ++++++++------- packages/decap-cms-app/CHANGELOG.md | 6 ++++++ packages/decap-cms-app/package.json | 6 +++--- packages/decap-cms-locales/CHANGELOG.md | 7 +++++++ packages/decap-cms-locales/package.json | 2 +- packages/decap-cms-widget-object/CHANGELOG.md | 6 ++++++ packages/decap-cms-widget-object/package.json | 2 +- packages/decap-cms/CHANGELOG.md | 6 ++++++ packages/decap-cms/package.json | 4 ++-- 9 files changed, 40 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef64582e56c7..9e1197d65412 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33157,12 +33157,12 @@ } }, "packages/decap-cms": { - "version": "3.4.0", + "version": "3.5.0", "license": "MIT", "dependencies": { "codemirror": "^5.46.0", "create-react-class": "^15.7.0", - "decap-cms-app": "^3.4.0", + "decap-cms-app": "^3.5.0", "decap-cms-media-library-cloudinary": "^3.0.3", "decap-cms-media-library-uploadcare": "^3.0.2", "file-loader": "^6.2.0", @@ -33172,7 +33172,7 @@ } }, "packages/decap-cms-app": { - "version": "3.4.0", + "version": "3.5.0", "license": "MIT", "dependencies": { "@emotion/react": "^11.11.1", @@ -33183,6 +33183,7 @@ "decap-cms-backend-azure": "^3.1.3", "decap-cms-backend-bitbucket": "^3.1.4", "decap-cms-backend-git-gateway": "^3.2.2", + "decap-cms-backend-gitea": "^3.1.5", "decap-cms-backend-github": "^3.2.2", "decap-cms-backend-gitlab": "^3.2.2", "decap-cms-backend-proxy": "^3.1.4", @@ -33192,7 +33193,7 @@ "decap-cms-lib-auth": "^3.0.5", "decap-cms-lib-util": "^3.1.0", "decap-cms-lib-widgets": "^3.1.0", - "decap-cms-locales": "^3.2.0", + "decap-cms-locales": "^3.3.0", "decap-cms-ui-default": "^3.1.4", "decap-cms-widget-boolean": "^3.1.3", "decap-cms-widget-code": "^3.1.4", @@ -33204,7 +33205,7 @@ "decap-cms-widget-map": "^3.1.4", "decap-cms-widget-markdown": "^3.2.0", "decap-cms-widget-number": "^3.1.3", - "decap-cms-widget-object": "^3.1.4", + "decap-cms-widget-object": "^3.2.0", "decap-cms-widget-relation": "^3.3.2", "decap-cms-widget-select": "^3.2.2", "decap-cms-widget-string": "^3.1.3", @@ -33585,7 +33586,7 @@ } }, "packages/decap-cms-locales": { - "version": "3.2.0", + "version": "3.3.0", "license": "MIT" }, "packages/decap-cms-media-library-cloudinary": { @@ -33887,7 +33888,7 @@ } }, "packages/decap-cms-widget-object": { - "version": "3.1.4", + "version": "3.2.0", "license": "MIT", "peerDependencies": { "@emotion/react": "^11.11.1", diff --git a/packages/decap-cms-app/CHANGELOG.md b/packages/decap-cms-app/CHANGELOG.md index c8e0115040cc..181afcbb5112 100644 --- a/packages/decap-cms-app/CHANGELOG.md +++ b/packages/decap-cms-app/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.5.0](https://github.com/decaporg/decap-cms/compare/decap-cms-app@3.4.0...decap-cms-app@3.5.0) (2025-01-15) + +### Bug Fixes + +- **build:** Fix ESM output ([#7357](https://github.com/decaporg/decap-cms/issues/7357)) ([#7358](https://github.com/decaporg/decap-cms/issues/7358)) ([6cd7cb3](https://github.com/decaporg/decap-cms/commit/6cd7cb3b41718e20b44acaae1e2ffd73129520c2)) + # [3.4.0](https://github.com/decaporg/decap-cms/compare/decap-cms-app@3.3.3...decap-cms-app@3.4.0) (2024-11-12) **Note:** Version bump only for package decap-cms-app diff --git a/packages/decap-cms-app/package.json b/packages/decap-cms-app/package.json index e5ce603dfe54..52fa65eca3c2 100644 --- a/packages/decap-cms-app/package.json +++ b/packages/decap-cms-app/package.json @@ -1,7 +1,7 @@ { "name": "decap-cms-app", "description": "An extensible, open source, Git-based, React CMS for static sites. Reusable congiuration with React as peer.", - "version": "3.4.0", + "version": "3.5.0", "homepage": "https://www.decapcms.org", "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-app", "bugs": "https://github.com/decaporg/decap-cms/issues", @@ -45,7 +45,7 @@ "decap-cms-lib-auth": "^3.0.5", "decap-cms-lib-util": "^3.1.0", "decap-cms-lib-widgets": "^3.1.0", - "decap-cms-locales": "^3.2.0", + "decap-cms-locales": "^3.3.0", "decap-cms-ui-default": "^3.1.4", "decap-cms-widget-boolean": "^3.1.3", "decap-cms-widget-code": "^3.1.4", @@ -57,7 +57,7 @@ "decap-cms-widget-map": "^3.1.4", "decap-cms-widget-markdown": "^3.2.0", "decap-cms-widget-number": "^3.1.3", - "decap-cms-widget-object": "^3.1.4", + "decap-cms-widget-object": "^3.2.0", "decap-cms-widget-relation": "^3.3.2", "decap-cms-widget-select": "^3.2.2", "decap-cms-widget-string": "^3.1.3", diff --git a/packages/decap-cms-locales/CHANGELOG.md b/packages/decap-cms-locales/CHANGELOG.md index df4f80768a05..9a087070fcb7 100644 --- a/packages/decap-cms-locales/CHANGELOG.md +++ b/packages/decap-cms-locales/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.3.0](https://github.com/decaporg/decap-cms/compare/decap-cms-locales@3.2.0...decap-cms-locales@3.3.0) (2025-01-15) + +### Features + +- add mk (Macedonian) locale ([#7346](https://github.com/decaporg/decap-cms/issues/7346)) ([b41e43a](https://github.com/decaporg/decap-cms/commit/b41e43a73218f0950ea9c96f21dfe0a31aa173b3)) +- **locale/cs:** improve the Czech translations, add new strings from `en` ([#7326](https://github.com/decaporg/decap-cms/issues/7326)) ([76f4b74](https://github.com/decaporg/decap-cms/commit/76f4b7411684943c515a77932fcac743adce8733)) + # [3.2.0](https://github.com/decaporg/decap-cms/compare/decap-cms-locales@3.1.4...decap-cms-locales@3.2.0) (2024-08-07) ### Bug Fixes diff --git a/packages/decap-cms-locales/package.json b/packages/decap-cms-locales/package.json index 2a6c150590e7..e84bef19908d 100644 --- a/packages/decap-cms-locales/package.json +++ b/packages/decap-cms-locales/package.json @@ -1,7 +1,7 @@ { "name": "decap-cms-locales", "description": "Locales for Decap CMS.", - "version": "3.2.0", + "version": "3.3.0", "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-locales", "bugs": "https://github.com/decaporg/decap-cms/issues", "license": "MIT", diff --git a/packages/decap-cms-widget-object/CHANGELOG.md b/packages/decap-cms-widget-object/CHANGELOG.md index cdd922f2f7f7..c7bfc1350685 100644 --- a/packages/decap-cms-widget-object/CHANGELOG.md +++ b/packages/decap-cms-widget-object/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.2.0](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-object@3.1.4...decap-cms-widget-object@3.2.0) (2025-01-15) + +### Bug Fixes + +- **object-widget:** highlight nested validation errors ([#7330](https://github.com/decaporg/decap-cms/issues/7330)) ([0bb2904](https://github.com/decaporg/decap-cms/commit/0bb290492218e710e113cadccc49cdc475328db4)) + ## [3.1.4](https://github.com/decaporg/decap-cms/compare/decap-cms-widget-object@3.1.3...decap-cms-widget-object@3.1.4) (2024-08-13) ### Reverts diff --git a/packages/decap-cms-widget-object/package.json b/packages/decap-cms-widget-object/package.json index 737544707f8d..1bfddf240d0a 100644 --- a/packages/decap-cms-widget-object/package.json +++ b/packages/decap-cms-widget-object/package.json @@ -1,7 +1,7 @@ { "name": "decap-cms-widget-object", "description": "Widget for displaying an object of fields for Decap CMS.", - "version": "3.1.4", + "version": "3.2.0", "homepage": "https://www.decapcms.org/docs/widgets/#object", "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-widget-object", "bugs": "https://github.com/decaporg/decap-cms/issues", diff --git a/packages/decap-cms/CHANGELOG.md b/packages/decap-cms/CHANGELOG.md index 122d037347b0..b8a3511b2dc8 100644 --- a/packages/decap-cms/CHANGELOG.md +++ b/packages/decap-cms/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [3.5.0](https://github.com/decaporg/decap-cms/compare/decap-cms@3.4.0...decap-cms@3.5.0) (2025-01-15) + +### Bug Fixes + +- **build:** Fix ESM output ([#7357](https://github.com/decaporg/decap-cms/issues/7357)) ([#7358](https://github.com/decaporg/decap-cms/issues/7358)) ([6cd7cb3](https://github.com/decaporg/decap-cms/commit/6cd7cb3b41718e20b44acaae1e2ffd73129520c2)) + # [3.4.0](https://github.com/decaporg/decap-cms/compare/decap-cms@3.3.3...decap-cms@3.4.0) (2024-11-12) **Note:** Version bump only for package decap-cms diff --git a/packages/decap-cms/package.json b/packages/decap-cms/package.json index af8033e23d79..3b46f8be8dc9 100644 --- a/packages/decap-cms/package.json +++ b/packages/decap-cms/package.json @@ -1,7 +1,7 @@ { "name": "decap-cms", "description": "An extensible, open source, Git-based, React CMS for static sites.", - "version": "3.4.0", + "version": "3.5.0", "homepage": "https://www.decapcms.org", "repository": "https://github.com/decaporg/decap-cms", "bugs": "https://github.com/decaporg/decap-cms/issues", @@ -22,7 +22,7 @@ "dependencies": { "codemirror": "^5.46.0", "create-react-class": "^15.7.0", - "decap-cms-app": "^3.4.0", + "decap-cms-app": "^3.5.0", "decap-cms-media-library-cloudinary": "^3.0.3", "decap-cms-media-library-uploadcare": "^3.0.2", "file-loader": "^6.2.0", From 47a2f70ef788ae8e61bbbc0ac21e00d68d0029d0 Mon Sep 17 00:00:00 2001 From: Felix Gnass Date: Fri, 17 Jan 2025 13:13:58 +0100 Subject: [PATCH 6/6] feat(nested collections): allow non-index files (#7359) * feat(nested collections): allow non-index files This commit fixes #4972 to allow nested folders with additional content beyond an index file. Side effect: To keep the feature simple, this will now show index files as pages within a folder in NetlifyCMS. This enables creating additional files alongside the given index, but is a change in behavior from the current implementation. Co-authored-by: Eric Gade <105373963+eric-gade@users.noreply.github.com> * test(e2e): adapt to new way nested collections work We use regexps as otherwise .contains("Sub Directory") would also match "Another Sub Directory" --------- Co-authored-by: Andrew Dunkman Co-authored-by: Eric Gade <105373963+eric-gade@users.noreply.github.com> Co-authored-by: Anze Demsar --- .../editorial_workflow_spec_test_backend.js | 44 ++++++------ .../Collection/Entries/EntriesCollection.js | 15 ++-- .../__tests__/EntriesCollection.spec.js | 8 +-- .../EntriesCollection.spec.js.snap | 16 ++--- .../components/Collection/NestedCollection.js | 4 +- .../NestedCollection.spec.js.snap | 68 +++++++++++++++++++ 6 files changed, 108 insertions(+), 47 deletions(-) diff --git a/cypress/e2e/editorial_workflow_spec_test_backend.js b/cypress/e2e/editorial_workflow_spec_test_backend.js index 7d5fae36a968..7cd15ea7cffd 100644 --- a/cypress/e2e/editorial_workflow_spec_test_backend.js +++ b/cypress/e2e/editorial_workflow_spec_test_backend.js @@ -207,17 +207,16 @@ describe('Test Backend Editorial Workflow', () => { login(); inSidebar(() => cy.contains('a', 'Pages').click()); - inSidebar(() => cy.contains('a', 'Directory')); + inSidebar(() => cy.contains('a', /^Directory$/)); inGrid(() => cy.contains('a', 'Root Page')); - inGrid(() => cy.contains('a', 'Directory')); - inSidebar(() => cy.contains('a', 'Directory').click()); + inSidebar(() => cy.contains('a', /^Directory$/).click()); - inGrid(() => cy.contains('a', 'Sub Directory')); - inGrid(() => cy.contains('a', 'Another Sub Directory')); + inSidebar(() => cy.contains('a', /^Sub Directory$/)); + inSidebar(() => cy.contains('a', 'Another Sub Directory')); - inSidebar(() => cy.contains('a', 'Sub Directory').click()); - inGrid(() => cy.contains('a', 'Nested Directory')); + inSidebar(() => cy.contains('a', /^Sub Directory$/).click()); + inSidebar(() => cy.contains('a', 'Nested Directory')); cy.url().should( 'eq', 'http://localhost:8080/#/collections/pages/filter/directory/sub-directory', @@ -233,21 +232,17 @@ describe('Test Backend Editorial Workflow', () => { login(); inSidebar(() => cy.contains('a', 'Pages').click()); - inSidebar(() => cy.contains('a', 'Directory').click()); - inGrid(() => cy.contains('a', 'Another Sub Directory').click()); - - cy.url().should( - 'eq', - 'http://localhost:8080/#/collections/pages/entries/directory/another-sub-directory/index', - ); + inSidebar(() => cy.contains('a', /^Directory$/).click()); + inSidebar(() => cy.contains('a', 'Another Sub Directory').click()); + inGrid(() => cy.contains('a', 'Another Sub Directory')); }); it(`can create a new entry with custom path`, () => { login(); inSidebar(() => cy.contains('a', 'Pages').click()); - inSidebar(() => cy.contains('a', 'Directory').click()); - inSidebar(() => cy.contains('a', 'Sub Directory').click()); + inSidebar(() => cy.contains('a', /^Directory$/).click()); + inSidebar(() => cy.contains('a', /^Sub Directory$/).click()); cy.contains('a', 'New Page').click(); cy.get('[id^="path-field"]').should('have.value', 'directory/sub-directory'); @@ -262,9 +257,9 @@ describe('Test Backend Editorial Workflow', () => { publishEntryInEditor(publishTypes.publishNow); exitEditor(); - inGrid(() => cy.contains('a', 'New Path Title')); - inSidebar(() => cy.contains('a', 'Directory').click()); - inSidebar(() => cy.contains('a', 'Directory').click()); + inSidebar(() => cy.contains('a', 'New Path Title')); + inSidebar(() => cy.contains('a', /^Directory$/).click()); + inSidebar(() => cy.contains('a', /^Directory$/).click()); inGrid(() => cy.contains('a', 'New Path Title').should('not.exist')); }); @@ -272,8 +267,8 @@ describe('Test Backend Editorial Workflow', () => { login(); inSidebar(() => cy.contains('a', 'Pages').click()); - inSidebar(() => cy.contains('a', 'Directory').click()); - inSidebar(() => cy.contains('a', 'Sub Directory').click()); + inSidebar(() => cy.contains('a', /^Directory$/).click()); + inSidebar(() => cy.contains('a', /^Sub Directory$/).click()); cy.contains('a', 'New Page').click(); cy.get('[id^="title-field"]').type('New Path Title'); @@ -292,7 +287,8 @@ describe('Test Backend Editorial Workflow', () => { login(); inSidebar(() => cy.contains('a', 'Pages').click()); - inGrid(() => cy.contains('a', 'Directory').click()); + inSidebar(() => cy.contains('a', /^Directory$/).click()); + inGrid(() => cy.contains('a', /^Directory$/).click()); cy.get('[id^="path-field"]').should('have.value', 'directory'); cy.get('[id^="path-field"]').clear(); @@ -310,7 +306,7 @@ describe('Test Backend Editorial Workflow', () => { inSidebar(() => cy.contains('a', 'New Directory').click()); - inGrid(() => cy.contains('a', 'Sub Directory')); - inGrid(() => cy.contains('a', 'Another Sub Directory')); + inSidebar(() => cy.contains('a', /^Sub Directory$/)); + inSidebar(() => cy.contains('a', 'Another Sub Directory')); }); }); diff --git a/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js b/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js index 830f7911de8c..55aa3c2bf61d 100644 --- a/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js +++ b/packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js @@ -119,20 +119,19 @@ export class EntriesCollection extends React.Component { export function filterNestedEntries(path, collectionFolder, entries) { const filtered = entries.filter(e => { - const entryPath = e.get('path').slice(collectionFolder.length + 1); + let entryPath = e.get('path').slice(collectionFolder.length + 1); if (!entryPath.startsWith(path)) { return false; } - // only show immediate children + // for subdirectories, trim off the parent folder corresponding to + // this nested collection entry if (path) { - // non root path - const trimmed = entryPath.slice(path.length + 1); - return trimmed.split('/').length === 2; - } else { - // root path - return entryPath.split('/').length <= 2; + entryPath = entryPath.slice(path.length + 1); } + + // only show immediate children + return !entryPath.includes('/'); }); return filtered; } diff --git a/packages/decap-cms-core/src/components/Collection/Entries/__tests__/EntriesCollection.spec.js b/packages/decap-cms-core/src/components/Collection/Entries/__tests__/EntriesCollection.spec.js index 5afbd19b19e2..174bc779370b 100644 --- a/packages/decap-cms-core/src/components/Collection/Entries/__tests__/EntriesCollection.spec.js +++ b/packages/decap-cms-core/src/components/Collection/Entries/__tests__/EntriesCollection.spec.js @@ -45,11 +45,11 @@ describe('filterNestedEntries', () => { ]; const entries = fromJS(entriesArray); expect(filterNestedEntries('dir3', 'src/pages', entries).toJS()).toEqual([ - { slug: 'dir3/dir4/index', path: 'src/pages/dir3/dir4/index.md', data: { title: 'File 4' } }, + { slug: 'dir3/index', path: 'src/pages/dir3/index.md', data: { title: 'File 3' } }, ]); }); - it('should return immediate children and root for root path', () => { + it('should return only immediate children for root path', () => { const entriesArray = [ { slug: 'index', path: 'src/pages/index.md', data: { title: 'Root' } }, { slug: 'dir1/index', path: 'src/pages/dir1/index.md', data: { title: 'File 1' } }, @@ -60,8 +60,6 @@ describe('filterNestedEntries', () => { const entries = fromJS(entriesArray); expect(filterNestedEntries('', 'src/pages', entries).toJS()).toEqual([ { slug: 'index', path: 'src/pages/index.md', data: { title: 'Root' } }, - { slug: 'dir1/index', path: 'src/pages/dir1/index.md', data: { title: 'File 1' } }, - { slug: 'dir3/index', path: 'src/pages/dir3/index.md', data: { title: 'File 3' } }, ]); }); }); @@ -126,7 +124,7 @@ describe('EntriesCollection', () => { expect(asFragment()).toMatchSnapshot(); }); - it('should render apply filter term for nested collections', () => { + it('should render with applied filter term for nested collections', () => { const entriesArray = [ { slug: 'index', path: 'src/pages/index.md', data: { title: 'Root' } }, { slug: 'dir1/index', path: 'src/pages/dir1/index.md', data: { title: 'File 1' } }, diff --git a/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap b/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap index ccc4d5911e89..c833607a1fff 100644 --- a/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap +++ b/packages/decap-cms-core/src/components/Collection/Entries/__tests__/__snapshots__/EntriesCollection.spec.js.snap @@ -1,36 +1,36 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EntriesCollection should render apply filter term for nested collections 1`] = ` +exports[`EntriesCollection should render connected component 1`] = ` `; -exports[`EntriesCollection should render connected component 1`] = ` +exports[`EntriesCollection should render show only immediate children for nested collection 1`] = ` `; -exports[`EntriesCollection should render show only immediate children for nested collection 1`] = ` +exports[`EntriesCollection should render with applied filter term for nested collections 1`] = ` diff --git a/packages/decap-cms-core/src/components/Collection/NestedCollection.js b/packages/decap-cms-core/src/components/Collection/NestedCollection.js index 0cc9a068c870..d2ff36c5052e 100644 --- a/packages/decap-cms-core/src/components/Collection/NestedCollection.js +++ b/packages/decap-cms-core/src/components/Collection/NestedCollection.js @@ -80,7 +80,7 @@ function TreeNode(props) { const sortedData = sortBy(treeData, getNodeTitle); return sortedData.map(node => { - const leaf = node.children.length <= 1 && !node.children[0]?.isDir && depth > 0; + const leaf = node.children.length === 0 && depth > 0; if (leaf) { return null; } @@ -90,7 +90,7 @@ function TreeNode(props) { } const title = getNodeTitle(node); - const hasChildren = depth === 0 || node.children.some(c => c.children.some(c => c.isDir)); + const hasChildren = depth === 0 || node.children.some(c => c.isDir); return ( diff --git a/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/NestedCollection.spec.js.snap b/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/NestedCollection.spec.js.snap index dd3f5fcd55ac..b643246f4411 100644 --- a/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/NestedCollection.spec.js.snap +++ b/packages/decap-cms-core/src/components/Collection/__tests__/__snapshots__/NestedCollection.spec.js.snap @@ -138,6 +138,20 @@ exports[`NestedCollection should render connected component 1`] = ` margin-right: 4px; } +.emotion-6 { + position: relative; + top: 2px; + color: #fff; + width: 0; + height: 0; + border: 5px solid transparent; + border-radius: 2px; + border-left: 6px solid currentColor; + border-right: 0; + color: currentColor; + left: 2px; +} + File 1 +
.emotion-0 { @@ -207,6 +224,20 @@ exports[`NestedCollection should render connected component 1`] = ` margin-right: 4px; } +.emotion-6 { + position: relative; + top: 2px; + color: #fff; + width: 0; + height: 0; + border: 5px solid transparent; + border-radius: 2px; + border-left: 6px solid currentColor; + border-right: 0; + color: currentColor; + left: 2px; +} + File 2 +
@@ -367,6 +401,20 @@ exports[`NestedCollection should render correctly with nested entries 1`] = ` margin-right: 4px; } +.emotion-6 { + position: relative; + top: 2px; + color: #fff; + width: 0; + height: 0; + border: 5px solid transparent; + border-radius: 2px; + border-left: 6px solid currentColor; + border-right: 0; + color: currentColor; + left: 2px; +} + File 1 +
.emotion-0 { @@ -436,6 +487,20 @@ exports[`NestedCollection should render correctly with nested entries 1`] = ` margin-right: 4px; } +.emotion-6 { + position: relative; + top: 2px; + color: #fff; + width: 0; + height: 0; + border: 5px solid transparent; + border-radius: 2px; + border-left: 6px solid currentColor; + border-right: 0; + color: currentColor; + left: 2px; +} + File 2 +