diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 440ac41aab..560a050feb 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -41,13 +41,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -93,13 +93,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -145,13 +145,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -197,13 +197,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -249,13 +249,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -301,13 +301,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -352,13 +352,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -403,13 +403,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -456,13 +456,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:8081 http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -508,13 +508,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -585,13 +585,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -686,14 +686,14 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -746,13 +746,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000 http://localhost' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos @@ -800,13 +800,13 @@ jobs: wait-on: 'npx wait-on --httpTimeout 20000 http-get://127.0.0.1:55001/plone http://127.0.0.1:3000 http://localhost' # Upload Cypress screenshots - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-screenshots path: cypress/screenshots # Upload Cypress videos - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-videos diff --git a/Makefile b/Makefile index 67da9f5a35..8e974dc36e 100644 --- a/Makefile +++ b/Makefile @@ -192,7 +192,7 @@ start-frontend-docker: .PHONY: start-backend-docker-guillotina start-backend-docker-guillotina: - docker-compose -f g-api/docker-compose.yml up -d + docker compose -f g-api/docker-compose.yml up -d ##### Acceptance tests (Cypress) @@ -217,7 +217,7 @@ start-test-backend: ## Start Test Plone Backend (api folder) .PHONY: stop-backend-docker-guillotina stop-backend-docker-guillotina: - docker-compose -f g-api/docker-compose.yml down + docker compose -f g-api/docker-compose.yml down .PHONY: test-acceptance-server-old @@ -264,7 +264,7 @@ test-acceptance-seamless: ## Start Seamless Cypress Acceptance Tests .PHONY: start-test-acceptance-webserver-seamless start-test-acceptance-webserver-seamless: ## Start the seamless webserver - cd cypress/docker && docker-compose -f seamless.yml up + cd cypress/docker && docker compose -f seamless.yml up .PHONY: full-test-acceptance-seamless full-test-acceptance-seamless: ## Runs Seamless Core Full Acceptance Testing in headless mode @@ -374,7 +374,7 @@ full-test-acceptance-workingcopy: ## Runs WorkingCopy Full Acceptance Testing in .PHONY: start-test-acceptance-server-guillotina start-test-acceptance-server-guillotina: ## Start Guillotina Test Acceptance Server (docker container) - docker-compose -f g-api/docker-compose.yml up > /dev/null + docker compose -f g-api/docker-compose.yml up > /dev/null .PHONY: start-test-acceptance-frontend-guillotina start-test-acceptance-frontend-guillotina: ## Start the Guillotina Acceptance Frontend Fixture diff --git a/cypress/tests/core/blocks/listing/blocks-listing-templates.js b/cypress/tests/core/blocks/listing/blocks-listing-templates.js index 37af70bdcf..5de2f56cb5 100644 --- a/cypress/tests/core/blocks/listing/blocks-listing-templates.js +++ b/cypress/tests/core/blocks/listing/blocks-listing-templates.js @@ -34,11 +34,7 @@ describe('Folder Contents Tests', () => { cy.visit('/my-folder/my-document'); cy.get('.edit').click(); - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get( - '[style="transition: opacity 500ms ease 0ms;"] > :nth-child(2) > .ui', - ).click(); + cy.addNewBlock('listing'); cy.get('#field-variation').click().type('summary{enter}'); cy.get('#toolbar-save').click(); cy.wait('@content'); @@ -74,11 +70,7 @@ describe('Folder Contents Tests', () => { cy.visit('/my-folder/my-document'); cy.get('.edit').click(); - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get( - '[style="transition: opacity 500ms ease 0ms;"] > :nth-child(2) > .ui', - ).click(); + cy.addNewBlock('listing'); cy.get('#field-variation').click().type('summary{enter}'); cy.get('#toolbar-save').click(); cy.wait('@content'); @@ -116,11 +108,7 @@ describe('Folder Contents Tests', () => { cy.visit('/my-folder/my-document'); cy.get('.edit').click(); - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get( - '[style="transition: opacity 500ms ease 0ms;"] > :nth-child(2) > .ui', - ).click(); + cy.addNewBlock('listing'); cy.get('#field-variation').click().type('imageGallery{enter}'); cy.get('#toolbar-save').click(); cy.wait('@content'); diff --git a/g-api/README.md b/g-api/README.md index c86a915e31..601355925c 100644 --- a/g-api/README.md +++ b/g-api/README.md @@ -7,7 +7,7 @@ Configuration demonstrating how to run Volto with Guillotina as a backend. _Disclaimer:_ Guillotina doesn't support the full API/features that Plone provides. Contributors are welcome. ```shell -docker-compose -f g-api/docker-compose.yml up -d +docker compose -f g-api/docker-compose.yml up -d ``` or using the convenience makefile command: diff --git a/news/6248.feature b/news/6248.feature new file mode 100644 index 0000000000..8f809c135e --- /dev/null +++ b/news/6248.feature @@ -0,0 +1,2 @@ +The schema for the `ContentsPropertiesModal` can be enhanced using the `contentPropertiesSchemaEnhancer` setting. +Also, the properties form is now prepopulated with values if all selected items share the same value. @davisagli diff --git a/news/6250.bugfix b/news/6250.bugfix new file mode 100644 index 0000000000..ac84782906 --- /dev/null +++ b/news/6250.bugfix @@ -0,0 +1 @@ +Disable save button when loading POST query @sabrina-bongiovanni diff --git a/news/6253.internal b/news/6253.internal new file mode 100644 index 0000000000..6e9a9f948e --- /dev/null +++ b/news/6253.internal @@ -0,0 +1 @@ +Bump actions/upload-artifact to v4 and replace `docker-compose` with `docker compose`. @stevepiercy, @davisagli diff --git a/news/6273.bugfix b/news/6273.bugfix new file mode 100644 index 0000000000..6caa1e8e5e --- /dev/null +++ b/news/6273.bugfix @@ -0,0 +1 @@ +Fix error in `SortOn` component when no sort is selected. @davisagli diff --git a/packages/generator-volto/Makefile b/packages/generator-volto/Makefile index 61f23050cc..4d6d0328cf 100644 --- a/packages/generator-volto/Makefile +++ b/packages/generator-volto/Makefile @@ -29,4 +29,4 @@ test-acceptance-server: .PHONY: test-acceptance-guillotina test-acceptance-guillotina: - docker-compose -f g-api/docker-compose.yml up > /dev/null + docker compose -f g-api/docker-compose.yml up > /dev/null diff --git a/src/components/manage/Add/Add.jsx b/src/components/manage/Add/Add.jsx index e971749e5e..f5b7652da1 100644 --- a/src/components/manage/Add/Add.jsx +++ b/src/components/manage/Add/Add.jsx @@ -408,6 +408,7 @@ class Add extends Component { aria-label={this.props.intl.formatMessage(messages.save)} onClick={() => this.form.current.onSubmit()} loading={this.props.createRequest.loading} + disabled={this.props.createRequest.loading} > { const showSelectField = sortOnOptions.length > 1; if (!showSelectField && !activeSortOn) { - return; + return null; } const value = { value: activeSortOn || intl.formatMessage(messages.noSelection), diff --git a/src/components/manage/Contents/Contents.jsx b/src/components/manage/Contents/Contents.jsx index c462592977..cd1781e84d 100644 --- a/src/components/manage/Contents/Contents.jsx +++ b/src/components/manage/Contents/Contents.jsx @@ -1529,6 +1529,9 @@ class Contents extends Component { onCancel={this.onPropertiesCancel} onOk={this.onPropertiesOk} items={this.state.selected} + values={map(this.state.selected, (id) => + find(this.state.items, { '@id': id }), + ).filter((item) => item)} /> {this.state.showWorkflow && ( { - const { onCancel, onOk, open, items } = props; + const { onCancel, onOk, open, items, values } = props; const intl = useIntl(); const dispatch = useDispatch(); const request = useSelector((state) => state.content.update); const prevrequestloading = usePrevious(request.loading); + const baseSchema = { + fieldsets: [ + { + id: 'default', + title: intl.formatMessage(messages.default), + fields: [ + 'effective', + 'expires', + 'rights', + 'creators', + 'exclude_from_nav', + ], + }, + ], + properties: { + effective: { + description: intl.formatMessage(messages.effectiveDescription), + title: intl.formatMessage(messages.effectiveTitle), + type: 'string', + widget: 'datetime', + }, + expires: { + description: intl.formatMessage(messages.expiresDescription), + title: intl.formatMessage(messages.expiresTitle), + type: 'string', + widget: 'datetime', + }, + rights: { + description: intl.formatMessage(messages.rightsDescription), + title: intl.formatMessage(messages.rightsTitle), + type: 'string', + widget: 'textarea', + }, + creators: { + description: intl.formatMessage(messages.creatorsDescription), + title: intl.formatMessage(messages.creatorsTitle), + type: 'array', + }, + exclude_from_nav: { + description: intl.formatMessage(messages.excludeFromNavDescription), + title: intl.formatMessage(messages.excludeFromNavTitle), + type: 'boolean', + }, + }, + required: [], + }; + const schemaEnhancer = config.settings.contentPropertiesSchemaEnhancer; + let schema = schemaEnhancer + ? schemaEnhancer({ + schema: cloneDeepSchema(baseSchema), + intl, + }) + : baseSchema; + + const initialData = {}; + if (values?.length) { + for (const name of Object.keys(schema.properties)) { + const firstValue = values[0][name]; + // should not show floor or ceiling dates + if ( + (name === 'effective' && firstValue && firstValue <= '1970') || + (name === 'expires' && firstValue && firstValue >= '2499') + ) { + continue; + } + if (values.every((item) => item[name] === firstValue)) { + initialData[name] = firstValue; + } + } + } + useEffect(() => { if (prevrequestloading && request.loaded) { onOk(); @@ -78,13 +151,19 @@ const ContentsPropertiesModal = (props) => { }, [onOk, prevrequestloading, request.loaded]); const onSubmit = (data) => { - if (isEmpty(data)) { + let changes = {}; + for (const name of Object.keys(data)) { + if (data[name] !== initialData[name]) { + changes[name] = data[name]; + } + } + if (isEmpty(changes)) { onOk(); } else { dispatch( updateContent( items, - map(items, () => data), + map(items, () => changes), ), ); } @@ -97,54 +176,8 @@ const ContentsPropertiesModal = (props) => { onSubmit={onSubmit} onCancel={onCancel} title={intl.formatMessage(messages.properties)} - schema={{ - fieldsets: [ - { - id: 'default', - title: intl.formatMessage(messages.default), - fields: [ - 'effective', - 'expires', - 'rights', - 'creators', - 'exclude_from_nav', - ], - }, - ], - properties: { - effective: { - description: intl.formatMessage(messages.effectiveDescription), - title: intl.formatMessage(messages.effectiveTitle), - type: 'string', - widget: 'datetime', - }, - expires: { - description: intl.formatMessage(messages.expiresDescription), - title: intl.formatMessage(messages.expiresTitle), - type: 'string', - widget: 'datetime', - }, - rights: { - description: intl.formatMessage(messages.rightsDescription), - title: intl.formatMessage(messages.rightsTitle), - type: 'string', - widget: 'textarea', - }, - creators: { - description: intl.formatMessage(messages.creatorsDescription), - title: intl.formatMessage(messages.creatorsTitle), - type: 'array', - }, - exclude_from_nav: { - description: intl.formatMessage( - messages.excludeFromNavDescription, - ), - title: intl.formatMessage(messages.excludeFromNavTitle), - type: 'boolean', - }, - }, - required: [], - }} + schema={schema} + formData={initialData} /> ) ); diff --git a/src/config/index.js b/src/config/index.js index adb6197ba1..04e6702e74 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -185,6 +185,7 @@ let config = { ], showSelfRegistration: false, contentMetadataTagsImageField: 'image', + contentPropertiesSchemaEnhancer: null, hasWorkingCopySupport: false, maxUndoLevels: 200, // undo history size for the main form addonsInfo: addonsInfo,