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/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/cypress/e2e/markdown_widget_code_block_spec.js b/cypress/e2e/markdown_widget_code_block_spec.js index 1b835f67ac39..4ecce19bf10f 100644 --- a/cypress/e2e/markdown_widget_code_block_spec.js +++ b/cypress/e2e/markdown_widget_code_block_spec.js @@ -77,7 +77,8 @@ function codeBlock(content) {
-
+
+
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/cypress/utils/steps.js b/cypress/utils/steps.js index 464365f802b6..e392ed8c8acb 100644 --- a/cypress/utils/steps.js +++ b/cypress/utils/steps.js @@ -69,6 +69,7 @@ function assertColorOn(cssProperty, color, opts) { } else { (opts.scope ? opts.scope : cy) .contains('label', opts.label) + .parents() .next() .should(assertion); } @@ -518,23 +519,31 @@ function validateNestedListFields() { cy.contains('button', 'hotel locations').click(); cy.contains('button', 'cities').click(); cy.contains('label', 'City') + .parents() .next() + .first() .type('Washington DC'); cy.contains('label', 'Number of Hotels in City') + .parents() .next() + .first() .type('5'); cy.contains('button', 'city locations').click(); // add second city list item cy.contains('button', 'cities').click(); cy.contains('label', 'Cities') + .parents() .next() + .first() .find('div[class*=SortableListItem]') .eq(2) .as('secondCitiesListControl'); cy.get('@secondCitiesListControl') .contains('label', 'City') + .parents() .next() + .first() .type('Boston'); cy.get('@secondCitiesListControl') .contains('button', 'city locations') @@ -561,21 +570,25 @@ function validateNestedListFields() { // list control aliases cy.contains('label', 'Hotel Locations') + .parents() .next() .find('div[class*=SortableListItem]') .first() .as('hotelLocationsListControl'); cy.contains('label', 'Cities') + .parents() .next() .find('div[class*=SortableListItem]') .eq(0) .as('firstCitiesListControl'); cy.contains('label', 'City Locations') + .parents() .next() .find('div[class*=SortableListItem]') .eq(0) .as('firstCityLocationsListControl'); cy.contains('label', 'Cities') + .parents() .next() .find('div[class*=SortableListItem]') .eq(3) @@ -589,7 +602,9 @@ function validateNestedListFields() { assertListControlErrorStatus([colorError, colorError], '@secondCityLocationsListControl'); cy.contains('label', 'Hotel Name') + .parents() .next() + .first() .type('The Ritz Carlton'); cy.contains('button', 'Save').click(); assertNotification(notifications.error.missingField); @@ -598,12 +613,20 @@ function validateNestedListFields() { // fill out rest of form and save cy.get('@secondCitiesListControl') .contains('label', 'Number of Hotels in City') + .parents() + .next() + .first() .type(3); cy.get('@secondCitiesListControl') .contains('label', 'Hotel Name') + .parents() + .next() + .first() .type('Grand Hyatt'); cy.contains('label', 'Country') + .parents() .next() + .first() .type('United States'); flushClockAndSave(); assertNotification(notifications.saved); 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-lock.json b/package-lock.json index 6a6bd37b7b83..9e1197d65412 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7186,11 +7186,6 @@ "redux": "^4.0.5" } }, - "node_modules/@types/retry": { - "version": "0.12.0", - "dev": true, - "license": "MIT" - }, "node_modules/@types/scheduler": { "version": "0.16.5", "license": "MIT" @@ -10264,9 +10259,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001646", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz", - "integrity": "sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==", + "version": "1.0.30001647", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001647.tgz", + "integrity": "sha512-n83xdNiyeNcHpzWY+1aFbqCK7LuLfBricc4+alSQL2Xb6OR3XpnQAmlDG+pQcdTfiHRuLcQ96VOfrPSGiNJYSg==", "dev": true, "funding": [ { @@ -25432,6 +25427,12 @@ "node": ">=8" } }, + "node_modules/p-retry/node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, "node_modules/p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", @@ -33156,12 +33157,12 @@ } }, "packages/decap-cms": { - "version": "3.3.3", + "version": "3.5.0", "license": "MIT", "dependencies": { "codemirror": "^5.46.0", "create-react-class": "^15.7.0", - "decap-cms-app": "^3.3.3", + "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", @@ -33171,7 +33172,7 @@ } }, "packages/decap-cms-app": { - "version": "3.3.3", + "version": "3.5.0", "license": "MIT", "dependencies": { "@emotion/react": "^11.11.1", @@ -33182,16 +33183,17 @@ "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", "decap-cms-backend-test": "^3.1.3", - "decap-cms-core": "^3.4.2", + "decap-cms-core": "^3.5.0", "decap-cms-editor-component-image": "^3.1.3", "decap-cms-lib-auth": "^3.0.5", "decap-cms-lib-util": "^3.1.0", - "decap-cms-lib-widgets": "^3.0.2", - "decap-cms-locales": "^3.2.0", + "decap-cms-lib-widgets": "^3.1.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", @@ -33201,9 +33203,9 @@ "decap-cms-widget-image": "^3.1.3", "decap-cms-widget-list": "^3.2.2", "decap-cms-widget-map": "^3.1.4", - "decap-cms-widget-markdown": "^3.1.6", + "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", @@ -33412,7 +33414,7 @@ } }, "packages/decap-cms-core": { - "version": "3.4.2", + "version": "3.5.0", "license": "MIT", "dependencies": { "@iarna/toml": "2.2.5", @@ -33573,7 +33575,7 @@ } }, "packages/decap-cms-lib-widgets": { - "version": "3.0.2", + "version": "3.1.0", "license": "MIT", "dependencies": { "dayjs": "^1.11.10" @@ -33584,7 +33586,7 @@ } }, "packages/decap-cms-locales": { - "version": "3.2.0", + "version": "3.3.0", "license": "MIT" }, "packages/decap-cms-media-library-cloudinary": { @@ -33741,7 +33743,7 @@ } }, "packages/decap-cms-widget-markdown": { - "version": "3.1.6", + "version": "3.2.0", "license": "MIT", "dependencies": { "dompurify": "^2.2.6", @@ -33886,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/package.json b/package.json index d3512378aa40..4c8a1300b45a 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,10 @@ "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", "test:ci": "npm run lint-quiet && npm run type-check && npm run test:unit", "test:unit": "cross-env NODE_ENV=test jest --no-cache", "test:e2e": "npm run build:demo && npm run test:e2e:run", diff --git a/packages/decap-cms-app/CHANGELOG.md b/packages/decap-cms-app/CHANGELOG.md index d78b210eb4c3..181afcbb5112 100644 --- a/packages/decap-cms-app/CHANGELOG.md +++ b/packages/decap-cms-app/CHANGELOG.md @@ -3,6 +3,16 @@ 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 + ## [3.3.3](https://github.com/decaporg/decap-cms/compare/decap-cms-app@3.3.2...decap-cms-app@3.3.3) (2024-08-30) **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 a0398e7fabf2..52fa65eca3c2 100644 --- a/packages/decap-cms-app/package.json +++ b/packages/decap-cms-app/package.json @@ -1,10 +1,11 @@ { "name": "decap-cms-app", "description": "An extensible, open source, Git-based, React CMS for static sites. Reusable congiuration with React as peer.", - "version": "3.3.3", + "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", + "module": "dist/esm/index.js", "main": "dist/decap-cms-app.js", "files": [ "src/", @@ -34,16 +35,17 @@ "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", "decap-cms-backend-test": "^3.1.3", - "decap-cms-core": "^3.4.2", + "decap-cms-core": "^3.5.0", "decap-cms-editor-component-image": "^3.1.3", "decap-cms-lib-auth": "^3.0.5", "decap-cms-lib-util": "^3.1.0", - "decap-cms-lib-widgets": "^3.0.2", - "decap-cms-locales": "^3.2.0", + "decap-cms-lib-widgets": "^3.1.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", @@ -53,9 +55,9 @@ "decap-cms-widget-image": "^3.1.3", "decap-cms-widget-list": "^3.2.2", "decap-cms-widget-map": "^3.1.4", - "decap-cms-widget-markdown": "^3.1.6", + "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-core/CHANGELOG.md b/packages/decap-cms-core/CHANGELOG.md index a8b83c650e17..9a4a6376dace 100644 --- a/packages/decap-cms-core/CHANGELOG.md +++ b/packages/decap-cms-core/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-core@3.4.2...decap-cms-core@3.5.0) (2024-11-12) + +### Bug Fixes + +- clear field error in Editor after the field value is changed ([#7216](https://github.com/decaporg/decap-cms/issues/7216)) ([d9655ea](https://github.com/decaporg/decap-cms/commit/d9655eae8cc47fb3a63815f75321710b5192c6ef)) + ## [3.4.2](https://github.com/decaporg/decap-cms/compare/decap-cms-core@3.4.1...decap-cms-core@3.4.2) (2024-08-13) ### Reverts diff --git a/packages/decap-cms-core/package.json b/packages/decap-cms-core/package.json index 999ba27cc22f..59654f5a0fe4 100644 --- a/packages/decap-cms-core/package.json +++ b/packages/decap-cms-core/package.json @@ -1,7 +1,7 @@ { "name": "decap-cms-core", "description": "Decap CMS core application, see decap-cms package for the main distribution.", - "version": "3.4.2", + "version": "3.5.0", "repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-core", "bugs": "https://github.com/decaporg/decap-cms/issues", "module": "dist/esm/index.js", diff --git a/packages/decap-cms-core/src/actions/entries.ts b/packages/decap-cms-core/src/actions/entries.ts index d7971b854d06..e8529b7c20b0 100644 --- a/packages/decap-cms-core/src/actions/entries.ts +++ b/packages/decap-cms-core/src/actions/entries.ts @@ -431,8 +431,11 @@ export function changeDraftFieldValidation( }; } -export function clearFieldErrors() { - return { type: DRAFT_CLEAR_ERRORS }; +export function clearFieldErrors(uniqueFieldId: string) { + return { + type: DRAFT_CLEAR_ERRORS, + payload: { uniqueFieldId }, + }; } export function localBackupRetrieved(entry: EntryValue) { 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
+
diff --git a/packages/decap-cms-core/src/components/Editor/EditorControlPane/EditorControl.js b/packages/decap-cms-core/src/components/Editor/EditorControlPane/EditorControl.js index 217f4447ffe8..58dcd65e4241 100644 --- a/packages/decap-cms-core/src/components/Editor/EditorControlPane/EditorControl.js +++ b/packages/decap-cms-core/src/components/Editor/EditorControlPane/EditorControl.js @@ -62,7 +62,6 @@ const styleStrings = { disabled: ` pointer-events: none; opacity: 0.5; - background: #ccc; `, hidden: ` visibility: hidden; @@ -77,18 +76,26 @@ const ControlContainer = styled.div` } `; +const ControlTopbar = styled.div` + display: flex; + justify-content: space-between; + gap: 20px; + align-items: end; +`; const ControlErrorsList = styled.ul` list-style-type: none; font-size: 12px; color: ${colors.errorText}; - margin-bottom: 8px; text-align: right; + text-transform: uppercase; font-weight: 600; + margin: 0; + padding: 2px 0 3px; `; export const ControlHint = styled.p` margin-bottom: 0; - padding: 3px 0; + padding: 6px 0 0; font-size: 12px; color: ${props => props.error ? colors.errorText : props.active ? colors.active : colors.controlLabel}; @@ -238,28 +245,30 @@ class EditorControl extends React.Component { ${isHidden && styleStrings.hidden}; `} > - {widget.globalStyles && } - {errors && ( - - {errors.map( - error => - error.message && - typeof error.message === 'string' && ( -
  • - {error.message} -
  • - ), - )} -
    - )} - + + {widget.globalStyles && } + + {errors && ( + + {errors.map( + error => + error.message && + typeof error.message === 'string' && ( +
  • + {error.message} +
  • + ), + )} +
    + )} +
    onChange(field, newValue, newMetadata)} + onChange={(newValue, newMetadata) => { + onChange(field, newValue, newMetadata); + clearFieldErrors(this.uniqueFieldId); // Видаляємо помилки лише для цього поля + }} onValidate={onValidate && partial(onValidate, this.uniqueFieldId)} onOpenMediaLibrary={openMediaLibrary} onClearMediaControl={clearMediaControl} diff --git a/packages/decap-cms-core/src/components/Editor/EditorToolbar.js b/packages/decap-cms-core/src/components/Editor/EditorToolbar.js index e909e93055b1..c4be48281c07 100644 --- a/packages/decap-cms-core/src/components/Editor/EditorToolbar.js +++ b/packages/decap-cms-core/src/components/Editor/EditorToolbar.js @@ -594,11 +594,20 @@ export class EditorToolbar extends React.Component { , currentStatus ? [ - this.renderWorkflowStatusControls(), - this.renderNewEntryWorkflowPublishControls({ canCreate, canPublish }), + + {this.renderWorkflowStatusControls()} + {!hasChanged && this.renderNewEntryWorkflowPublishControls({ canCreate, canPublish })} + , ] - : !isNewEntry && - this.renderExistingEntryWorkflowPublishControls({ canCreate, canPublish, canDelete }), + : !isNewEntry && ( + + {this.renderExistingEntryWorkflowPublishControls({ + canCreate, + canPublish, + canDelete, + })} + + ), (!showDelete || useOpenAuthoring) && !hasUnpublishedChanges && !isModification ? null : ( - withReact(withHistory(withShortcodes(withBlocks(withLists(withInlines(createEditor())))))), + withHtml( + withReact(withHistory(withShortcodes(withBlocks(withLists(withInlines(createEditor())))))), + ), [], ); diff --git a/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/html/withHtml.js b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/html/withHtml.js new file mode 100644 index 000000000000..9dc9a5d73af7 --- /dev/null +++ b/packages/decap-cms-widget-markdown/src/MarkdownControl/plugins/html/withHtml.js @@ -0,0 +1,117 @@ +// source: https://github.com/ianstormtaylor/slate/blob/main/site/examples/ts/paste-html.tsx +import { jsx } from 'slate-hyperscript'; +import { Transforms } from 'slate'; + +const ELEMENT_TAGS = { + A: el => ({ type: 'link', url: el.getAttribute('href') }), + BLOCKQUOTE: () => ({ type: 'quote' }), + H1: () => ({ type: 'heading-one' }), + H2: () => ({ type: 'heading-two' }), + H3: () => ({ type: 'heading-three' }), + H4: () => ({ type: 'heading-four' }), + H5: () => ({ type: 'heading-five' }), + H6: () => ({ type: 'heading-six' }), + IMG: el => ({ type: 'image', url: el.getAttribute('src') }), + LI: () => ({ type: 'list-item' }), + OL: () => ({ type: 'numbered-list' }), + P: () => ({ type: 'paragraph' }), + PRE: () => ({ type: 'code' }), + UL: () => ({ type: 'bulleted-list' }), +}; + +// COMPAT: `B` is omitted here because Google Docs uses `` in weird ways. +const TEXT_TAGS = { + CODE: () => ({ code: true }), + DEL: () => ({ strikethrough: true }), + EM: () => ({ italic: true }), + I: () => ({ italic: true }), + S: () => ({ strikethrough: true }), + STRONG: () => ({ bold: true }), + U: () => ({ underline: true }), +}; + +const INLINE_STYLES = { + 'font-style': value => (value === 'italic' ? { italic: true } : {}), + 'font-weight': value => (value === 'bold' || parseInt(value, 10) >= 600 ? { bold: true } : {}), +}; + +function deserialize(el) { + if (el.nodeType === 3) { + return el.textContent; + } else if (el.nodeType !== 1) { + return null; + } else if (el.nodeName === 'BR') { + return '\n'; + } + + const { nodeName } = el; + let parent = el; + + if (nodeName === 'PRE' && el.childNodes[0] && el.childNodes[0].nodeName === 'CODE') { + parent = el.childNodes[0]; + } + let children = Array.from(parent.childNodes).map(deserialize).flat(); + + if (children.length === 0) { + children = [{ text: '' }]; + } + + if (el.nodeName === 'BODY') { + return jsx('fragment', {}, children); + } + + if (ELEMENT_TAGS[nodeName]) { + const attrs = ELEMENT_TAGS[nodeName](el); + return jsx('element', attrs, children); + } + + if (TEXT_TAGS[nodeName]) { + const attrs = TEXT_TAGS[nodeName](el); + return children.map(child => jsx('text', attrs, child)); + } + + // Convert inline CSS on span elements generated by Google Docs + if (nodeName === 'SPAN') { + const attrs = {}; + for (let i = 0; i < el.style.length; i++) { + const propertyName = el.style[i]; + if (INLINE_STYLES[propertyName]) { + const propertyValue = el.style.getPropertyValue(propertyName); + const propertyStyle = INLINE_STYLES[propertyName](propertyValue); + Object.assign(attrs, propertyStyle); + } + } + return children.map(child => jsx('text', attrs, child)); + } + + return children; +} + +function withHtml(editor) { + const { insertData, isInline, isVoid } = editor; + + editor.isInline = element => { + return element.type === 'link' ? true : isInline(element); + }; + + editor.isVoid = element => { + return element.type === 'image' ? true : isVoid(element); + }; + + editor.insertData = data => { + const html = data.getData('text/html'); + + if (html) { + const parsed = new DOMParser().parseFromString(html, 'text/html'); + const fragment = deserialize(parsed.body); + Transforms.insertFragment(editor, fragment); + return; + } + + insertData(data); + }; + + return editor; +} + +export default withHtml; diff --git a/packages/decap-cms-widget-markdown/src/MarkdownControl/renderers.js b/packages/decap-cms-widget-markdown/src/MarkdownControl/renderers.js index fe825e3dbd0a..9bc0e4c810de 100644 --- a/packages/decap-cms-widget-markdown/src/MarkdownControl/renderers.js +++ b/packages/decap-cms-widget-markdown/src/MarkdownControl/renderers.js @@ -226,8 +226,8 @@ function NumberedList(props) { } function Link(props) { - const url = props.url; - const title = props.title || url; + const url = props.element.url; + const title = props.element.title || url; return ( diff --git a/packages/decap-cms-widget-markdown/src/serializers/slateRemark.js b/packages/decap-cms-widget-markdown/src/serializers/slateRemark.js index b1bae20ffb9f..c75fb69fee32 100644 --- a/packages/decap-cms-widget-markdown/src/serializers/slateRemark.js +++ b/packages/decap-cms-widget-markdown/src/serializers/slateRemark.js @@ -450,7 +450,7 @@ export default function slateToRemark(value, { voidCodeBlock }) { */ case 'link': { const { title, data } = node; - return u(typeMap[node.type], { url: data?.url, title, ...data }, children); + return u(typeMap[node.type], { url: node.url, title, ...data }, children); } /** 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-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} diff --git a/packages/decap-cms/CHANGELOG.md b/packages/decap-cms/CHANGELOG.md index 158b43c44366..b8a3511b2dc8 100644 --- a/packages/decap-cms/CHANGELOG.md +++ b/packages/decap-cms/CHANGELOG.md @@ -3,6 +3,16 @@ 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 + ## [3.3.3](https://github.com/decaporg/decap-cms/compare/decap-cms@3.3.2...decap-cms@3.3.3) (2024-08-30) **Note:** Version bump only for package decap-cms diff --git a/packages/decap-cms/package.json b/packages/decap-cms/package.json index 8ce9e2a21996..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.3.3", + "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.3.3", + "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", 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'; /**