diff --git a/CHANGELOG.md b/CHANGELOG.md index 52693bfbaf..2a24031530 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 14.2.1 (unreleased) +## 14.7.2 (unreleased) ### Breaking @@ -10,8 +10,106 @@ ### Bugfix +- Fix the a11y violation of UrlWidget @iRohitSingh + +### Internal + +## 14.7.1 (2022-02-02) + +### Internal + +- Add CSS body class in Babel view. Improve marker for language independent fields in Babel view too. @sneridagh + +### Docs + +Update documentation for internal proxy & other smaller reorganisation for quicker onboarding of +new users/evaluators. @fredvd + +## 14.7.0 (2022-01-28) + +### Feature + +- Add `` and `` components. Check their Storybook stories for details. This is part of ongoing work to minimize the use of 'deprecated' momentjs. @sneridagh @tiberiuichim + +### Internal + +- Upgrade jest to latest release, 27 major. @tiberiuichim +- Lazyload momentjs. `parseDateTime` helper now requires passing the momentjs library @tiberiuichim + +## 14.6.0 (2022-01-27) + +### Feature + +- Use `volto.config.js` as dynamic configuration for addons. It adds up to the `package.json` `addons` key, allowing dynamic load of addons (eg. via environment variables) @sneridagh + ### Internal +- Fix ObjectListWidget story bug caused by lazyloading dnd libraries + @tiberiuichim + +## 14.5.0 (2022-01-26) + +### Feature + +- VocabularyTermsWidget: Token is now on creation of term editable, but stays ineditable afterwards. @ksuess + +### Bugfix + +- Fix A11Y violations in Navigation @iRohitSingh +- Fix `language-independent-field` CSS class styling @sneridagh + +### Internal + +- Lazyload react-beautiful-dnd @tiberiuichim +- Lazyload react-dnd @tiberiuichim +- Improve docs on environment variables, add recipes @sneridagh +- Update p.restapi to 8.20.0 and plone.volto to 4.0.0a1 and plone.rest to 2.0.0a2 @sneridagh + +## 14.4.0 (2022-01-21) + +### Feature + +- Language independent fields support in Volto forms @sneridagh + +## 14.3.0 (2022-01-20) + +### Feature + +- Bump semantic-ui-react to v2.0.3 @nileshgulia1 + +## 14.2.3 (2022-01-20) + +### Bugfix + +- Fix ListingBlock to add "No results" message when there are no messages @erral +- Fix overflow table in Content view @giuliaghisini +- Fixed url validation in FormValidation to admit ip addresses. @giuliaghisini +- Upgrade to plone.restapi 8.19.0 (to support the language independent fields serialization) @sneridagh + +## 14.2.2 (2022-01-13) + +### Bugfix + +- Fix home URL item in Navigation, which was evaluating as non-internal @sneridagh +- Improve the request handling in `getAPIResourceWithAuth` and in `Api` helper. This fixes the "Cannot set headers once the content has being sent" @sneridagh +- Fix when you remove the time from DatetimeWidget @iRohitSingh + +### Internal + +- Fix URL for Climate-Energy, a Volto website @tiberiuichim +- Fix quirky Cypress test in "DX control panel schema" (see https://github.com/plone/volto/runs/4803206906?check_suite_focus=true) @sneridagh + +## 14.2.1 (2022-01-12) + +### Bugfix + +- Fix home URL item in Navigation, which was evaluating as non-internal + +### Internal + +- Use plone-backend docker images for Cypress tests @sneridagh +- Upgrade `query-string` library so it supports Plone `:list` qs marker @sneridagh + ## 14.2.0 (2022-01-04) ### Feature diff --git a/Makefile b/Makefile index 307b7cb3e0..1f3aad5c8a 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,8 @@ MAKEFLAGS+=--no-builtin-rules # Project settings INSTANCE_PORT=8080 -KGS=plone.restapi=8.18.0 plone.app.iterate=4.0.2 plone.rest=2.0.0a1 plone.app.vocabularies=4.3.0 +DOCKER_IMAGE=plone/plone-backend:5.2.6 +KGS=plone.restapi==8.20.0 plone.volto==4.0.0a2 plone.rest==2.0.0a2 plone.app.iterate==4.0.2 plone.app.vocabularies==4.3.0 # Recipe snippets for reuse @@ -90,7 +91,7 @@ start-backend: ## Start Plone Backend .PHONY: start-backend-docker start-backend-docker: - docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e ADDONS="plone.volto" -e ZCML="plone.volto.cors" plone + docker run -it --rm -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE) .PHONY: start-backend-docker-guillotina start-backend-docker-guillotina: @@ -121,20 +122,20 @@ stop-backend-docker-guillotina: .PHONY: test-acceptance-server test-acceptance-server: - docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e VERSIONS="$(KGS)" -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors -e ADDONS='plone.app.robotframework plone.app.contenttypes plone.restapi plone.volto' plone ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING + docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS) plone.app.robotframework plone.app.contenttypes' -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors $(DOCKER_IMAGE) ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING .PHONY: test-acceptance-server-multilingual test-acceptance-server-multilingual: - docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e VERSIONS="$(KGS)" -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:multilingual -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors -e ADDONS='plone.app.robotframework plone.app.contenttypes plone.restapi plone.volto' plone ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING + docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS) plone.app.robotframework plone.app.contenttypes' -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:multilingual -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors $(DOCKER_IMAGE) ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING .PHONY: test-acceptance-server-workingcopy test-acceptance-server-workingcopy: - docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e VERSIONS="$(KGS)" -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.app.iterate:default,plone.volto:default-homepage -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors -e ADDONS='plone.app.robotframework plone.app.contenttypes plone.restapi plone.app.iterate plone.volto' plone ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING + docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS) plone.app.robotframework plone.app.contenttypes' -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.app.iterate:default,plone.volto:default-homepage -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors $(DOCKER_IMAGE) ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING # ZSERVER_PORT=55001 CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.app.iterate,plone.volto,plone.volto.cors APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.app.iterate:default,plone.volto:default-homepage ./api/bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING .PHONY: test-acceptance-server-coresandbox test-acceptance-server-coresandbox: - docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e VERSIONS="$(KGS)" -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage,plone.volto:coresandbox -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors,plone.volto.coresandbox -e ADDONS='plone.app.robotframework plone.app.contenttypes plone.restapi plone.app.iterate plone.volto' plone ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING + docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS) plone.app.robotframework plone.app.contenttypes' -e APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage,plone.volto:coresandbox -e CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors,plone.volto.coresandbox $(DOCKER_IMAGE) ./bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING # ZSERVER_PORT=55001 CONFIGURE_PACKAGES=plone.app.contenttypes,plone.restapi,plone.volto,plone.volto.cors,plone.volto.coresandbox APPLY_PROFILES=plone.app.contenttypes:plone-content,plone.restapi:default,plone.volto:default-homepage,plone.volto:coresandbox ./api/bin/robot-server plone.app.robotframework.testing.PLONE_ROBOT_TESTING .PHONY: test-acceptance-server-old diff --git a/README.md b/README.md index 7dea6aa5fd..0d7d8836b8 100644 --- a/README.md +++ b/README.md @@ -76,20 +76,41 @@ follow the prompts questions, provide `myvoltoproject` as project name then, whe We recommend Plone as backend of choice for Volto. -You can bootstrap a ready Docker Plone container with all the dependencies and ready for Volto use: +You can bootstrap a ready Docker Plone container with all the dependencies and ready for Volto use. We recommend to use the Plone docker builds based in `pip` [plone/plone-backend](https://github.com/plone/plone-backend) image: ```shell -docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e VERSIONS="plone.restapi=8.16.2 plone.app.iterate=4.0.2 plone.rest=2.0.0a1 plone.app.vocabularies=4.3.0" -e ADDONS="plone.volto" -e ZCML="plone.volto.cors" -e PROFILES="plone.volto:default-homepage" plone +docker run -it --rm --name=plone -p 8080:8080 -e SITE=Plone -e ADDONS="plone.restapi==8.18.0 plone.app.iterate==4.0.2 plone.rest==2.0.0a1 plone.app.vocabularies==4.3.0 plone.volto==3.1.0a7" -e PROFILES="plone.volto:default-homepage" plone/plone-backend ``` or as an alternative if you have experience with Plone and you have all the -dependencies installed on your system, you can use the supplied buildout in the +dependencies installed on your system, you can use the supplied convenience buildout in the `api` folder by issuing the command: ```shell make build-backend ``` +#### Recommended Plone version + +Volto is Plone 6 default UI, so it will work for all Plone 6 released versions. + +For the Plone 5 series latest released version (with Python 3) and above is recommended (at the time of writing 5.2.6). + +The following KGS (or above) are also recommended, for any Plone version used. + +#### KGS (known good versions) for backend packages + +Volto always works best with latest versions of the "Frontend stack" or at least the recommended ones (in parenthesis) which are: + +- plone.restapi (8.18.0) +- plone.rest (2.0.0a1) +- plone.volto (3.1.0a7) + +and the following core packages since some features require up to date versions: + +- plone.app.iterate (4.0.2) +- plone.app.vocabularies (4.3.0) + ### Start Volto ```shell @@ -109,7 +130,7 @@ Volto is actively developed since 2017 and used in production since 2018 on the - [Excellence at Humboldt-Universität zu Berlin](https://www.alles-beginnt-mit-einer-frage.de) (Website for the excellence initiative of the [Humboldt University Berlin](https://hu-berlin.de), developed by [kitconcept GmbH](https://kitconcept.com), 2019) - [Forest Information System for Europe](https://forest.eea.europa.eu) (Thematic website focusing on European forests, developed by [Eau de Web](https://www.eaudeweb.ro), 2019) - [Industrial Emissions portal for Europe](https://industry.eea.europa.eu) (Thematic website focusing on European industrial emissions, developed by [Eau de Web](https://www.eaudeweb.ro), 2020) -- [Energy Climate Union portal for Europe](https://demo-energy-union.eea.europa.eu) (Thematic website focusing on European strides towards mitigating climate change, developed by [Eau de Web](https://www.eaudeweb.ro), 2020) +- [Energy Climate Union portal for Europe](https://climate-energy.eea.europa.eu/) (Thematic website focusing on European strides towards mitigating climate change, developed by [Eau de Web](https://www.eaudeweb.ro), 2020) - [Talke Carrer Website](https://karriere.talke.com/) (Carrer website for [Talke](https://www.talke.com), one of the leading a chemical and petrochemical logistics companies in Germany, developed by [kitconcept GmbH](https://kitconcept.com), 2020) - [Stradanove](http://www.stradanove.it/) (Website of the Department of Youth Policies of the Municipality of Modena, developed by [RedTurtle](https://redturtle.it), 2020) - [VisitModena](https://www.visitmodena.it/) (Tourist website of the Municipality of Modena, developed by [RedTurtle](https://redturtle.it), 2020) diff --git a/__tests__/addon-registry.test.js b/__tests__/addon-registry.test.js index 9b72b19801..cd768dd8dc 100644 --- a/__tests__/addon-registry.test.js +++ b/__tests__/addon-registry.test.js @@ -26,6 +26,7 @@ describe('AddonConfigurationRegistry', () => { 'test-addon', 'test-released-addon', 'test-released-source-addon', + 'my-volto-config-addon', 'test-released-dummy', 'test-released-unmentioned', ]); @@ -60,6 +61,13 @@ describe('AddonConfigurationRegistry', () => { name: 'test-released-unmentioned', packageJson: `${base}/node_modules/test-released-unmentioned/package.json`, }, + 'my-volto-config-addon': { + addons: ['test-released-dummy'], + isPublishedPackage: false, + modulePath: `${base}/addons/my-volto-config-addon/src`, + name: 'my-volto-config-addon', + packageJson: `${base}/addons/my-volto-config-addon/package.json`, + }, 'test-released-dummy': { addons: ['test-released-unmentioned'], isPublishedPackage: false, @@ -74,6 +82,7 @@ describe('AddonConfigurationRegistry', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); const reg = new AddonConfigurationRegistry(base); expect(reg.getResolveAliases()).toStrictEqual({ + 'my-volto-config-addon': `${base}/addons/my-volto-config-addon/src`, 'test-addon': `${base}/addons/test-addon/src`, 'test-released-addon': `${base}/node_modules/test-released-addon`, 'test-released-dummy': `${base}/addons/test-released-dummy`, @@ -98,6 +107,7 @@ describe('AddonConfigurationRegistry', () => { 'test-addon', 'test-released-addon', 'test-released-source-addon', + 'my-volto-config-addon', ]); }); diff --git a/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/package.json b/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/package.json new file mode 100644 index 0000000000..af60c35be6 --- /dev/null +++ b/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/package.json @@ -0,0 +1,9 @@ +{ + "name": "test-addon", + "customizationPaths": [ + "src/custom-addons" + ], + "addons": [ + "test-released-dummy" + ] +} diff --git a/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/src/testaddon.js b/__tests__/fixtures/test-volto-project/addons/my-volto-config-addon/src/testaddon.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/__tests__/fixtures/test-volto-project/jsconfig.json b/__tests__/fixtures/test-volto-project/jsconfig.json index 094a71076a..a2cf60be48 100644 --- a/__tests__/fixtures/test-volto-project/jsconfig.json +++ b/__tests__/fixtures/test-volto-project/jsconfig.json @@ -2,7 +2,8 @@ "compilerOptions": { "paths": { "test-addon": ["test-addon/src"], - "test-released-dummy": ["test-released-dummy"] + "test-released-dummy": ["test-released-dummy"], + "my-volto-config-addon": ["my-volto-config-addon/src"] }, "baseUrl": "addons" } diff --git a/__tests__/fixtures/test-volto-project/volto.config.js b/__tests__/fixtures/test-volto-project/volto.config.js new file mode 100644 index 0000000000..ec05e8e085 --- /dev/null +++ b/__tests__/fixtures/test-volto-project/volto.config.js @@ -0,0 +1,3 @@ +module.exports = { + addons: ['my-volto-config-addon'], +}; diff --git a/addon-registry.js b/addon-registry.js index 86ace741df..0294f520e7 100644 --- a/addon-registry.js +++ b/addon-registry.js @@ -99,13 +99,26 @@ class AddonConfigurationRegistry { projectRootPath, 'package.json', ))); + // Loads the dynamic config, if any + if (fs.existsSync(path.join(projectRootPath, 'volto.config.js'))) { + this.voltoConfigJS = require(path.join( + projectRootPath, + 'volto.config.js', + )); + } else { + this.voltoConfigJS = []; + } + this.resultantMergedAddons = [ + ...(packageJson.addons || []), + ...(this.voltoConfigJS.addons || []), + ]; this.projectRootPath = projectRootPath; this.voltoPath = packageJson.name === '@plone/volto' ? `${projectRootPath}` : `${projectRootPath}/node_modules/@plone/volto`; - this.addonNames = (packageJson.addons || []).map((s) => s.split(':')[0]); + this.addonNames = this.resultantMergedAddons.map((s) => s.split(':')[0]); this.packages = {}; this.customizations = new Map(); @@ -114,7 +127,7 @@ class AddonConfigurationRegistry { this.initTestingPackages(); this.dependencyGraph = buildDependencyGraph( - packageJson.addons || [], + this.resultantMergedAddons, (name) => { this.initPublishedPackage(name); return this.packages[name].addons || []; diff --git a/api/buildout.cfg b/api/buildout.cfg index 4f8eaeec13..9d23011f17 100644 --- a/api/buildout.cfg +++ b/api/buildout.cfg @@ -74,13 +74,14 @@ eggs = ${instance:eggs} setuptools = zc.buildout = # force to latest p.restapi -plone.restapi = 8.18.0 +plone.restapi = 8.20.0 # new JSON only traversal -plone.rest = 2.0.0a1 +plone.rest = 2.0.0a2 # Using working copy support in Volto requires it plone.app.iterate = 4.0.2 # plone.volto requires it plone.app.vocabularies = 4.3.0 +pyOpenSSL = 21.0.0 plone.volto = robotframework = diff --git a/cypress.json b/cypress.json index 3b9ef0cf1f..8a519d507b 100644 --- a/cypress.json +++ b/cypress.json @@ -3,5 +3,6 @@ "viewportWidth": 1280, "ignoreTestFiles": ["*~"], "integrationFolder": "cypress/tests/core", - "chromeWebSecurity": false + "chromeWebSecurity": false, + "projectId": "5iy5e2" } diff --git a/cypress/tests/core-sandbox/search.js b/cypress/tests/core-sandbox/search.js index 411b450575..993c8b876f 100644 --- a/cypress/tests/core-sandbox/search.js +++ b/cypress/tests/core-sandbox/search.js @@ -32,9 +32,7 @@ context('Search action tests', () => { cy.navigate('/newsitem/edit'); // Add subject - cy.get('a:contains("Categorization")') - .click() - .get('.field-wrapper-subjects input') + cy.get('.field-wrapper-subjects input') .type('garden', { force: true }) .type('{enter}'); cy.get('#toolbar-save').click(); diff --git a/cypress/tests/core/content.js b/cypress/tests/core/content.js index 417faf3e1a..ef0f70e816 100644 --- a/cypress/tests/core/content.js +++ b/cypress/tests/core/content.js @@ -127,9 +127,22 @@ describe('Add Content Tests', () => { cy.url().should('eq', Cypress.config().baseUrl + '/my-folder'); cy.get('.navigation .item.active').should('have.text', 'My Folder'); }); + it('As editor I am setting the time in datetimeWidget', function () { + // when I add a Event + cy.get('#toolbar-add').click(); + cy.get('#toolbar-add-event').click(); + cy.get('#field-title').type('datetimeWidget test'); + cy.get('#start-time').click(); + cy.get('.rc-time-picker-panel-input').click(); + cy.get('.rc-time-picker-panel-input').clear().type('6:40 AM'); + cy.get('#toolbar-save').click(); - it('As editor I can add a Link (with an external link)', function () { + // then + + cy.get('.documentFirstHeading').should('have.text', 'datetimeWidget test'); + }); + it('As editor I can add a Link (with an external link)', function () { // When I add a link cy.get('#toolbar-add').click(); cy.get('#toolbar-add-link').click(); @@ -152,7 +165,6 @@ describe('Add Content Tests', () => { }); it('As editor I can add a Link (with an internal link)', function () { - // Given a Document "Link Target" cy.createContent({ contentType: 'Document', @@ -184,5 +196,4 @@ describe('Add Content Tests', () => { cy.url().should('eq', Cypress.config().baseUrl + '/link-target'); cy.get('main').contains('Link Target'); }); - }); diff --git a/cypress/tests/core/dexterity-controlpanel-schema.js b/cypress/tests/core/dexterity-controlpanel-schema.js index 0fee0b6a50..fa4a1c48af 100644 --- a/cypress/tests/core/dexterity-controlpanel-schema.js +++ b/cypress/tests/core/dexterity-controlpanel-schema.js @@ -30,6 +30,7 @@ describe('ControlPanel: Dexterity Content-Types Schema', () => { // Add field cy.get('button[id=addfield]').click(); + cy.wait(1000); cy.get('.modal .react-select-container').click().type('Choice{enter}'); cy.get('.modal input[id="field-title"]') .type('Color') diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 394fb6d3b7..864f35cd9b 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -20,17 +20,15 @@ extra_css: nav: - Getting Started: - - Bootstrap Volto: 'getting-started/install.md' + - Installing Volto: 'getting-started/install.md' - Developer roadmap: 'getting-started/roadmap.md' - Learning resources: 'getting-started/others.md' - - How does it work under the hood: 'getting-started/howdoesitwork.md' - - Design principles: 'design-principles/index.md' - - Style Guide: 'style-guide/index.md' - Configuration: - What is configurable?: 'configuration/how-to.md' - Settings reference guide: 'configuration/settings-reference.md' - Zero config builds: 'configuration/zero-config-builds.md' - Internal proxy: 'configuration/internalproxy.md' + - Dynamic Volto Addons Configuration: 'configuration/volto-config-js.md' - Backend configuration: 'configuration/backend.md' - Richeditor settings: 'configuration/richeditor-settings.md' - Multilingual: 'configuration/multilingual.md' @@ -79,6 +77,8 @@ nav: - Performance improvements: 'deploying/performance.md' - Upgrade Guide: 'upgrade-guide/index.md' - Developer Guidelines: + - Design principles: 'developer-guidelines/design-principles.md' + - Style Guide: 'developer-guidelines/style-guide.md' - Language features: 'developer-guidelines/language-features.md' - Linting: 'developer-guidelines/linting.md' - React: 'developer-guidelines/react.md' diff --git a/docs/source/configuration/backend.md b/docs/source/configuration/backend.md index 71d4482ead..d51fed8d6d 100644 --- a/docs/source/configuration/backend.md +++ b/docs/source/configuration/backend.md @@ -1,22 +1,20 @@ # Backend configuration ## plone.volto -In order to fully support all Volto features, the Plone backend, needs to be prepared for -Volto. This involves configuration, add-ons installation and some patches to the core. -The add'on `plone.volto` does all the heavy lifting for you and it's ready to use -in your own projects. We used it in our Getting Started section. +<<<<<<< HEAD +In order to fully support all Volto features, the Plone backend content API needs to be prepared for Volto. The add'on `plone.volto` does all the heavy lifting for you and is ready to use in your own projects. We used it in our Getting Started section. -However, this package is oppinionated and might not fit your needs, so if you want to -use your own integration package instead, just take a look at the features it provides, +This package is slightly opinionated but provides the correct default settings for when +you want to start with Volto. If you have advanced needs or want to move the setting to +your own integration package instead, just take a look at the features it provides, copy the ones you need for your project and create your own integration package. https://github.com/plone/plone.volto !!! tip From Volto 5.1 and above, Volto features an internal proxy to your API server. So - you don't have to deal with CORS. It's enabled by default, pointing to the server - specified in the `devProxyToApiPath` Volto settings (http://localhost:8080/Plone). - See [here](../configuration/internalproxy.md) for more details. + you don't have to deal with CORS issues. It's enabled by default, pointing to the server specified in the `devProxyToApiPath` Volto settings + (http://localhost:8080/Plone). See [here](../configuration/internalproxy.md) for more information. ## Install a Plone backend locally without Docker diff --git a/docs/source/configuration/environmentvariables.md b/docs/source/configuration/environmentvariables.md index 0de4608efc..790c0dc475 100644 --- a/docs/source/configuration/environmentvariables.md +++ b/docs/source/configuration/environmentvariables.md @@ -15,7 +15,29 @@ In the frontend we can access this variable with: window.env.RAZZLE_MY_VARIABLE ``` -## RAZZLE_LEGACY_TRAVERSE +## Runtime environment variables + +All the environment variables that are configurable work at runtime, not only at build time. This works since Volto 13 onwards. + +!!! info +Before Volto 13, you'd do: + + ```bash + RAZZLE_API_PATH=https://plone.org yarn build && yarn start:prod + ``` + + From Volto 13 onwards, you can now do: + + ```bash + yarn build && RAZZLE_API_PATH=https://plone.org yarn start:prod + ``` + +This brings you a lot of power since you don't have to rebuild on every config change. You can also generate builds on your CI, then deploy them anywhere. + + +## Environment variables reference + +### RAZZLE_LEGACY_TRAVERSE From Volto 14 onwards, Seamless mode is the recommended way of setting up your depoloyments. However, it forces you to upgrade several packages in the backend (`plone.restapi` and `plone.rest`) and adjust your web server configuration accordingly. @@ -25,7 +47,7 @@ In case you can't afford or change your deployment, you can still upgrade Volto RAZZLE_LEGACY_TRAVERSE=true yarn start:prod ``` -## VOLTO_ROBOTSTXT +### VOLTO_ROBOTSTXT You can override the robots.txt file with an environment variable called `VOLTO_ROBOTSTXT`. This is useful when using the same build on multiple @@ -40,7 +62,6 @@ Disallow: /" yarn start If you want to use the `VOLTO_ROBOTSTXT` environment variable, make sure to delete the file `public/robots.txt` from your project. - ### DEBUG It will enable the log several logging points scattered through the Volto code. It uses the `volto:` namespace. diff --git a/docs/source/configuration/how-to.md b/docs/source/configuration/how-to.md index a3c4ec90a2..b5793c2bb9 100644 --- a/docs/source/configuration/how-to.md +++ b/docs/source/configuration/how-to.md @@ -14,9 +14,10 @@ like: const absoluteUrl = `${config.settings.apiPath}/${content.url}` ``` -Both add-ons and projects can extend Volto's configuration registry. First the add-ons -configuration is applied, in the order they are defined in `package.json`, then finally -the project configuration is applied. Visualized like a pipe would be: +Both the main project and individual add-ons can extend Volto's configuration registry. +First the add-ons configuration is applied, in the order they are defined in +`package.json`, then finally the project configuration is applied. Visualized like +a pipe would be: > Default Volto configuration -> Add-on 1 -> Add-on 2 -> ... -> Add-on n -> Project diff --git a/docs/source/configuration/internalproxy.md b/docs/source/configuration/internalproxy.md index d449db798b..dda1e48bed 100644 --- a/docs/source/configuration/internalproxy.md +++ b/docs/source/configuration/internalproxy.md @@ -1,28 +1,41 @@ -# Internal proxy to API +# Internal proxy to content backend API + +The server side Volto SSR process (based on Razzle) has an internal proxy to the backend API +enabled by default. -While in development, Volto has an internal proxy to the backend API enabled by default. It provides a better developer experience out of the box, so the developer doesn't has to deal with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) and can focus on develop/test drive/demo Volto. -## Configuration +To understand the need for the internal proxy, there are three processes running in a Volto website: + +1. A frontend web application running in your browser (Javascript) +2. A Node.js server process that delivers the javascript to the client and does + Server Side Rendering (SSR) of your pages on first request (Javascript, the + Razzle package is used for SSR) +3. A Plone server process that stores and delivers all content through a REST API (Python) -By default, Volto expects a Plone backend located at `http://localhost:8080/Plone`. -These are the settings that allows you to configure the API and how the proxy works: +## Configuration -- `apiPath` [API_PATH] - URL of the backend, used through Volto. By default, the proxy URL. -- `devProxyToApiPath` - The real backend URL, used by the proxy. By default, `http://localhost:8080/Plone` +The default values from Volto configuration expect a Plone content backend located at `http://localhost:8080/Plone`. -!!! tip - You don't want to deal with CORS in your production deployments, so using the proxy is only meant to be enabled in development mode (e.g `yarn start`). However, for convenience and for testing/demoing using the stock build, it's also enabled in production mode since Volto 14. +What happens in the default development configuration/setup: -!!! note - You can disable the proxy by redefining a new `apiPath` and redefining an empty - `devProxyToApiPath` setting. +* The client side Volto javascript files precooked HTML (SSR) is served from http://localhost:3000/ by the NodeJS server process +* The client javascript does API requests for content and other data on the same url at http://localhost:3000/++api++/ +* The NodeJS service its internal proxy requests the data from the Plone content backend api and delivers +back json to the frontend. +* The web browser application is happy, because all connections go through the same URL and no CORS related security issues will be triggered. -Here are some examples. +!!! tip + You could also use the internal proxy for production setups. For convenience and for + testing/demoing using the stock build, it is also enabled in production mode since + Volto 14. But it is bad for performance because the server side running Node process + is also responsable for generating the SSR HTML. With Nginx, Apache or another + 'reverse proxy' you can also create an internal API mount which is more suited for + that. For more deployment information see ['Seemless mode'](/deploying/seamless-mode) -### Redefining the proxy target +### Examples redefining the proxy target You can redefine the local proxy target by using the `RAZZLE_DEV_PROXY_API_PATH` or setting `devProxyToApiPath` in the configuration object (`src/config.js`). @@ -40,30 +53,8 @@ or use the environment variable: RAZZLE_DEV_PROXY_API_PATH=http://localhost:8081/mysite yarn start ``` -### Disabling the proxy - -```js -export const settings = { - ...defaultSettings, - apiPath: process.env.RAZZLE_API_PATH || `http://localhost:8081/mysite`, // for Plone - devProxyToApiPath: '', // Set it to '' for disabling the proxy -}; -``` - -or use the environment variable: -```bash -RAZZLE_DEV_PROXY_API_PATH= RAZZLE_API_PATH=http://localhost:8081/mysite yarn start -``` - -!!! tip - To view the existing configuration, add console.log(config) to the `applyConfig` function. This dumps the existing config to your terminal console. +This redefines the request path from the internal proxy of the server side Node proces to the Plone content backend API, but leaves the frontend Volto process making all content requests to http://locahost:3000/++api++/ ### Advanced usage -It's possible to define the proxy target more accuratelly using the `RAZZLE_PROXY_REWRITE_TARGET` environment variable, or the `proxyRewriteTarget` setting in the configuration object. - -This allows you to run Volto against an external (not local) site, e.g. for debugging purposes. In theory then, this is possible: - -```bash -RAZZLE_PROXY_REWRITE_TARGET=https://plone.org RAZZLE_DEV_PROXY_API_PATH=https://plone.org yarn start -``` +See [](../recipes/environment-variables.md) for recipes on internal proxy usage. diff --git a/docs/source/configuration/volto-config-js.md b/docs/source/configuration/volto-config-js.md new file mode 100644 index 0000000000..bddbca06f1 --- /dev/null +++ b/docs/source/configuration/volto-config-js.md @@ -0,0 +1,21 @@ +# Dynamic Volto Addons Configuration + +There are some cases where defining the Volto addons your project is going to use is not enough, and you need more control over it. For example, when you have several builds under the umbrella of the same project that share the core of the code, but each build have special requirements, like other CSS, customizations, shadowing or the features of other addons available. + +There is a scapehatch `volto.config.js`. This module exports an object and can have arbitrary code depending on your needs: + +```js +let addons = []; +if (process.env.MY_SPECIAL_CUSTOM_BUILD) { + addons = ['volto-custom-addon']; +} + +module.exports = { + addons: addons, +}; + +``` + +In the case above, we delegate to the presence of an environment variable (MY_SPECIAL_CUSTOM_BUILD) the use of the list of addons specified. + +This list, sums up to the one defined in `package.json` (it does not override it), and the addons added are placed at the end of the addons list, so the config in there is applied after the ones in the `package.json`. diff --git a/docs/source/style-guide/Quanta.pdf b/docs/source/developer-guidelines/Quanta.pdf similarity index 100% rename from docs/source/style-guide/Quanta.pdf rename to docs/source/developer-guidelines/Quanta.pdf diff --git a/docs/source/design-principles/index.md b/docs/source/developer-guidelines/design-principles.md similarity index 100% rename from docs/source/design-principles/index.md rename to docs/source/developer-guidelines/design-principles.md diff --git a/docs/source/style-guide/index.md b/docs/source/developer-guidelines/style-guide.md similarity index 100% rename from docs/source/style-guide/index.md rename to docs/source/developer-guidelines/style-guide.md diff --git a/docs/source/getting-started/howdoesitwork.md b/docs/source/getting-started/howdoesitwork.md deleted file mode 100644 index 6d39f0e36d..0000000000 --- a/docs/source/getting-started/howdoesitwork.md +++ /dev/null @@ -1,5 +0,0 @@ -# How does it work under the hood - -You can watch the talk during the World Plone Day 2021: - - diff --git a/docs/source/getting-started/install.md b/docs/source/getting-started/install.md index d92e5eee3f..3851ea3cdc 100644 --- a/docs/source/getting-started/install.md +++ b/docs/source/getting-started/install.md @@ -5,7 +5,7 @@ Volto can be installed in any operating system assuming that this requirements are met: -- [Node.js LTS (14.x)](https://nodejs.org/) +- [Node.js LTS (16.x)](https://nodejs.org/) - [Python 3.7.x / 3.8.x](https://python.org/) or - [Docker](https://www.docker.com/get-started) (if using the Plone/Guillotina docker images) @@ -13,18 +13,37 @@ are met: Depending on the OS that you are using some of the following might change, they are assuming a MacOS/Linux machine: +## Components / Processes running + +There are three processes continuously running when you have a working Volto website: + +1. A frontend web application running in your browser (Javascript) +2. A Node.js server process that delivers the javascript to the client and does + Server Side Rendering (SSR) of your pages on first request (Javascript, the + Razzle package is used for SSR) +3. A Plone server process that stores and delivers all content through a REST API (Python) + +When you start with Volto most of the first customisations you will want to make (or mabye +ever need to make) are in the javascript code used in the browser and Razzle process. Therefore +this getting started chapter will focus on installing a nodejs/javascript environment locally +and suggest you start the API backend using a container. + + ## Install nvm (NodeJS version manager) -If you have a working nodejs development setup on your machine, this step is -not required. But it's a good idea to integrate nvm for development, as it -provides easy access to any Nodejs released version. +If you have a working Node javascript development already set up on your machine or you prefer +another management tool to install/maintain node this step is not needed. If you have less +experience with setting up javascript, it's a good idea to integrate nvm for development, as +it provides easy access to any NodeJS released version. 1. Open a terminal console and type: ```bash touch ~/.bash_profile -curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash +curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.39.1/install.sh | bash ``` +(Please check the latest available version of nvm on the [main README](https://github.com/nvm-sh/nvm) + 2. Close the terminal and open a new one or execute: ``` source ~/.bash_profile @@ -50,10 +69,8 @@ node -v If you're using the fish shell, you can use [nvm.fish](https://github.com/jorgebucaran/nvm.fish) !!! note - Volto supports all currently active NodeJS LTS versions based on [NodeJS - Releases page](https://nodejs.org/en/about/releases/). On 2021-04-30 Volto - will not support Node 10 as it will reach its end of life. - + Volto supports currently active NodeJS LTS versions based on [NodeJS + Releases page](https://nodejs.org/en/about/releases/), starting with Node 12 LTS. ## Yarn (NodeJS package manager) Install the Yarn Classic version (not the 2.x one!), of the popular node package manager. @@ -102,17 +119,20 @@ should not throw an error and show the current running containers. ## Run a Volto ready Plone Docker container -When you have installed Docker, you can run an standard Plone Docker container with the proper configuration for Volto using the `plone.volto` add'on right away by issuing: +When you have installed Docker, you can use the official Plone Docker container with the proper configuration for Volto using the `plone.volto` add'on right away by issuing: + ```shell docker run -it --rm --name=plone \ - -p 8080:8080 -e SITE=Plone -e ADDONS="plone.volto" \ - -e ZCML="plone.volto.cors" \ + -p 8080:8080 -e SITE=Plone -e \ + ADDONS="plone.restapi==8.18.0 plone.app.iterate==4.0.2 plone.rest==2.0.0a1 plone.app.vocabularies==4.3.0 plone.volto==3.1.0a8" \ -e PROFILES="plone.volto:default-homepage" \ - -e VERSIONS="plone.restapi=8.16.2 plone.app.iterate=4.0.2 plone.rest=2.0.0a1 plone.app.vocabularies=4.3.0" \ - plone + plone/plone-backend ``` +!!! tip + This setup is meant only for demonstration and quick testing purposes (since it destroys the container on exit (--rm)). In case you need production ready deployment, check the latest [Plone Deployment Training](https://training.plone.org/5/plone-deployment/index.html). + !!! note The example above does not persist yet any changes you make through Volto in the Plone docker container backend! For this you need to map the /data directory diff --git a/docs/source/getting-started/others.md b/docs/source/getting-started/others.md index f3739fc8d2..7d462408a2 100644 --- a/docs/source/getting-started/others.md +++ b/docs/source/getting-started/others.md @@ -16,18 +16,26 @@ Javascript-centered trainings. - [React](https://training.plone.org/5/react/index.html) - [JavaScript For Plone Developers](https://training.plone.org/5/javascript/index.html) -## PloneConf 2021 +## How does it work under the hood -[Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkQDLQinBwdEXpebDTQCwdGi) +You can watch the talk during the World Plone Day 2021: -## PloneConf 2020 + -[Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkTJPayNdKIZ8lLDm5RVOLV3) +## Presentations at the last two PloneConf's -## PloneConf 2019 +In recent years the new Volto frontend for Plone has been presented in more and more talks from +our yearly Conferences: + +- [PloneConf 2021 Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkQDLQinBwdEXpebDTQCwdGi) + +- [PloneConf 2020 Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkTJPayNdKIZ8lLDm5RVOLV3) -PloneConf 2019 had several Volto-relevant presentations. +## PloneConf 2019 + +PloneConf 2019 and other events have several Volto-relevant presentations, but not stored in a +single playlist: ### Howtos - [Rob Gietema - How to create your own Volto site!](https://www.youtube.com/watch?v=3QLN8tsjjf4) diff --git a/docs/source/recipes/environment-variables.md b/docs/source/recipes/environment-variables.md new file mode 100644 index 0000000000..f51cc535c8 --- /dev/null +++ b/docs/source/recipes/environment-variables.md @@ -0,0 +1,93 @@ +# How to use environment variables + +## Configure your development environment + +By default, seamless mode works in development mode, but it assumes some sensible defaults +in order to ease configuration. These are the defaults: + +### Internal Proxy is enabled (to avoid dealing with CORS) + +That means that your backend will be available under `http://localhost:3000/++api++` + +RAZZLE_DEV_PROXY_API_PATH = 'http://localhost:8080/Plone' + +The internal proxy simulates a web server doing reverse proxy with standard Plone VHM config. +You can configure this if required too: + +RAZZLE_PROXY_REWRITE_TARGET = '/VirtualHostBase/http/localhost:3000/Plone/++api++/VirtualHostRoot' + +(this variable by default is parameterized like this: `/VirtualHostBase/http/${apiPathURL.hostname}:${apiPathURL.port}${instancePath}/++api++/VirtualHostRoot`) + +#### Your site is not in http://localhost:8080/Plone + +Let's assume you have your site in http://localhost:55001/Plone + +In order to simulate it, you could launch the backend like: + +``` +docker run -it --rm -p 55001:8080 -e SITE=Plone -e ADDONS='plone.restapi==8.20.0 plone.app.iterate==4.0.2 plone.rest==2.0.0a1 plone.app.vocabularies==4.3.0 plone.volto==3.1.0a8' plone/plone-backend:6.0.0a2 +``` + +``` +RAZZLE_DEV_PROXY_API_PATH='http://localhost:55001/Plone' yarn start +``` + +Let's say that you do have it in a customized site id: http://localhost:55001/myplonesite + +``` +docker run -it --rm -p 55001:8080 -e SITE=myplonesite -e ADDONS='plone.restapi==8.20.0 plone.app.iterate==4.0.2 plone.rest==2.0.0a1 plone.app.vocabularies==4.3.0 plone.volto==3.1.0a8' plone/plone-backend:6.0.0a2 +``` + +``` +RAZZLE_DEV_PROXY_API_PATH='http://localhost:55001/myplonesite' yarn start +``` + +### Debug an external site (provided you have access to it) + +!!! warning + This is an advanced feature, and needs understanding of what you are doing and which server are you accessing. Also, it depends on your server configuration. + +Let's say you want to debug a deployed site in production, but the build does not allow you to look deeper into the tracebacks. You could bootstrap a frontend in your machine, and point it to the production server, combining environment variables like: + +``` +RAZZLE_DEV_PROXY_API_PATH=https://2021.ploneconf.org RAZZLE_PROXY_REWRITE_TARGET=https://2021.ploneconf.org/++api++ yarn start +``` + +This has the drawback that could be that the proxy does not work well with the proxied SSL connection. + +If you have access (via tunnel) to the port of the deployed backend is even more easier: + +``` +RAZZLE_DEV_PROXY_API_PATH=http://2021.ploneconf.org:8080/Plone yarn start +``` + +This will use the internal proxy to access the backend, bypassing CORS. + +!!! important + Anything that would mean not using the internal proxy (eg. using RAZZLE_API_PATH) will have to deal with CORS. You could enable `plone.volto.cors` ZCML or add your config to the build to support bypassing CORS at a server level. Like this: + + https://github.com/plone/volto/blob/master/api/buildout.cfg#L29-L40 + + ``` + zcml-additional = + + + + ``` + +## Disabling the internal proxy + +The internal proxy is always available, even in production since Volto 14. + +When `RAZZLE_API_PATH` is present, Volto does not use it, and use the URL in there instead. + +!!! important + Again, when `RAZZLE_API_PATH` is present your deployment is the one who has to deal with CORS. diff --git a/docs/source/recipes/widget.md b/docs/source/recipes/widget.md index 418f617ace..9198321996 100644 --- a/docs/source/recipes/widget.md +++ b/docs/source/recipes/widget.md @@ -136,6 +136,11 @@ const applyConfig = (config) => { Based on this setup, Volto will render this field with the `TokenWidget`. + +!!! tip + See [storybook](docs.voltocms.com/storybook) with available widgets. + + ## Write a new widget !!! note diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index d986128ac0..b56be9b830 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -1345,6 +1345,11 @@ msgstr "Etiqueta" msgid "Language" msgstr "Llenguatge" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index ae984e1015..ad7a273831 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -1351,6 +1351,11 @@ msgstr "Label" msgid "Language" msgstr "Sprache" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index a826d44da2..bb0ce5bef3 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -1336,6 +1336,11 @@ msgstr "" msgid "Language" msgstr "" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index 923677a0c3..5bc73b0a89 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -1346,6 +1346,11 @@ msgstr "" msgid "Language" msgstr "Idioma" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index cb3d16fade..348d1e9057 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -1343,6 +1343,11 @@ msgstr "" msgid "Language" msgstr "Hizkuntza" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 7853dfb424..de70c88dea 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -1353,6 +1353,11 @@ msgstr "" msgid "Language" msgstr "Langage" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index 95304ff817..2a31534102 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -1336,6 +1336,11 @@ msgstr "Etichetta" msgid "Language" msgstr "Lingua" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "Campo indipendete dalla lingua" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" @@ -1933,7 +1938,7 @@ msgstr "il" #: components/manage/Form/UndoToolbar # defaultMessage: Redo msgid "Redo" -msgstr "" +msgstr "Ripeti" #: components/manage/Blocks/Table/Edit # defaultMessage: Minimalistic table design @@ -2718,12 +2723,12 @@ msgstr "Digita l'URL di un Video (YouTube, Vimeo or mp4)" #: components/manage/Widgets/SelectAutoComplete # defaultMessage: Type text... msgid "Type text..." -msgstr "" +msgstr "Digita il testo..." #: components/manage/Blocks/Text/Edit # defaultMessage: Type text… msgid "Type text…" -msgstr "Digita testo…" +msgstr "Digita il testo…" #: components/manage/Blocks/Title/Edit # defaultMessage: Type the title… @@ -2743,7 +2748,7 @@ msgstr "Non autorizzato" #: components/manage/Form/UndoToolbar # defaultMessage: Undo msgid "Undo" -msgstr "" +msgstr "Annulla" #: components/manage/BlockChooser/BlockChooser # defaultMessage: Unfold @@ -3503,7 +3508,7 @@ msgstr "Indice dei contenuti" #: helpers/MessageLabels/MessageLabels # defaultMessage: Input must be valid url (www.something.com or http(s)://www.something.com) msgid "url" -msgstr "URL" +msgstr "Il testo inserito deve essere un url valido (www.qualcosa.com oppure http(s)://www.qualcosa.com)" #: components/manage/Toolbar/PersonalTools # defaultMessage: user avatar diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index c6fb82331d..606b8790c9 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -1344,6 +1344,11 @@ msgstr "" msgid "Language" msgstr "言語" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index 4ee60fefe3..7e8c7acaa1 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -1355,6 +1355,11 @@ msgstr "" msgid "Language" msgstr "Taal" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index 5643cfd3cf..7e0bf2fb1b 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -1344,6 +1344,11 @@ msgstr "" msgid "Language" msgstr "Idioma" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index e33af376b7..3fe168e39e 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -1346,6 +1346,11 @@ msgstr "" msgid "Language" msgstr "Idioma" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index e4dfbb1f39..533d7001b2 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -1336,6 +1336,11 @@ msgstr "" msgid "Language" msgstr "Limba" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/locales/volto.pot b/locales/volto.pot index c1902934c5..ba61ae4168 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2021-12-04T18:51:01.959Z\n" +"POT-Creation-Date: 2022-01-20T14:52:55.940Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -1338,6 +1338,11 @@ msgstr "" msgid "Language" msgstr "" +#: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. +msgid "Language independent field." +msgstr "" + #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Last msgid "Last" diff --git a/package.json b/package.json index b65247e35d..ba0449ad94 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "14.2.0", + "version": "14.7.1", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" @@ -34,8 +34,8 @@ "start-spa": "razzle start --type=spa", "build": "razzle build", "build-spa": "razzle build --type=spa", - "test": "razzle test --env=jest-environment-jsdom-sixteen", - "test:ci": "CI=true razzle test --env=jest-environment-jsdom-sixteen", + "test": "razzle test --env=jest-environment-jsdom-sixteen --maxWorkers=50%", + "test:ci": "CI=true NODE_ICU_DATA=node_modules/full-icu razzle test --env=jest-environment-jsdom-sixteen", "test:husky": "CI=true yarn test --bail --findRelatedTests", "test:debug": "node --inspect node_modules/.bin/jest --runInBand", "start:prod": "NODE_ENV=production node build/server.js", @@ -342,7 +342,7 @@ "prismjs": "1.25.0", "promise-file-reader": "1.0.2", "prop-types": "15.7.2", - "query-string": "7.0.0", + "query-string": "7.1.0", "razzle": "3.4.5", "razzle-plugin-bundle-analyzer": "1.2.0", "rc-time-picker": "3.7.3", @@ -387,7 +387,7 @@ "release-it": "14.2.1", "rrule": "2.6.4", "semantic-ui-less": "2.4.1", - "semantic-ui-react": "0.88.1", + "semantic-ui-react": "2.0.3", "semver": "5.6.0", "serialize-javascript": "3.1.0", "start-server-and-test": "1.10.6", @@ -414,7 +414,9 @@ "@storybook/react": "^6.3.0", "babel-loader": "^8.1.0", "crypto-random-string": "3.2.0", + "full-icu": "1.4.0", "identity-obj-proxy": "3.0.0", + "jest": "27.4.5", "jest-environment-jsdom-sixteen": "1.0.3", "react-is": "^16.13.1", "tmp": "0.2.1", diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index c3961f35c2..d97f656a4d 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -4,6 +4,8 @@ ### Added +- Add mrs-developer.json in template + ### Changes ## 5.4.0 (2021-12-29) diff --git a/packages/generator-volto/generators/app/templates/mrs.developer.json b/packages/generator-volto/generators/app/templates/mrs.developer.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/packages/generator-volto/generators/app/templates/mrs.developer.json @@ -0,0 +1 @@ +{} diff --git a/razzle.config.js b/razzle.config.js index 9473d9556e..199e310407 100644 --- a/razzle.config.js +++ b/razzle.config.js @@ -227,7 +227,7 @@ const defaultModify = ({ include.push(fs.realpathSync(`${registry.voltoPath}/src`)); } // Add babel support external (ie. node_modules npm published packages) - if (packageJson.addons) { + if (registry.addonNames && registry.addonNames.length > 0) { registry.addonNames.forEach((addon) => { const p = fs.realpathSync(registry.packages[addon].modulePath); if (include.indexOf(p) === -1) { diff --git a/src/components/index.js b/src/components/index.js index 824ed15e61..52701c1aed 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -198,5 +198,7 @@ export ToCSettingsSchema from '@plone/volto/components/manage/Blocks/ToC/Schema' export MaybeWrap from '@plone/volto/components/manage/MaybeWrap/MaybeWrap'; export ContentMetadataTags from '@plone/volto/components/theme/ContentMetadataTags/ContentMetadataTags'; +export FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate'; +export FormattedRelativeDate from '@plone/volto/components/theme/FormattedDate/FormattedRelativeDate'; export App from '@plone/volto/components/theme/App/App'; diff --git a/src/components/manage/Actions/__snapshots__/Actions.test.jsx.snap b/src/components/manage/Actions/__snapshots__/Actions.test.jsx.snap index d02321aa2d..51c4c11017 100644 --- a/src/components/manage/Actions/__snapshots__/Actions.test.jsx.snap +++ b/src/components/manage/Actions/__snapshots__/Actions.test.jsx.snap @@ -9,6 +9,7 @@ exports[`Actions renders an actions component 1`] = ` onChange={[Function]} onClick={[Function]} onFocus={[Function]} + onKeyDown={[Function]} onMouseDown={[Function]} role="listbox" tabIndex={0} diff --git a/src/components/manage/Add/Add.jsx b/src/components/manage/Add/Add.jsx index 81d3efa43f..f517f5be4d 100644 --- a/src/components/manage/Add/Add.jsx +++ b/src/components/manage/Add/Add.jsx @@ -5,15 +5,13 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { Helmet } from '@plone/volto/helpers'; +import { BodyClass, Helmet } from '@plone/volto/helpers'; import { connect } from 'react-redux'; import { compose } from 'redux'; import { keys, isEmpty } from 'lodash'; import { defineMessages, injectIntl } from 'react-intl'; import { Button, Grid, Menu } from 'semantic-ui-react'; import { Portal } from 'react-portal'; -import { DragDropContext } from 'react-dnd'; -import HTML5Backend from 'react-dnd-html5-backend'; import { v4 as uuid } from 'uuid'; import qs from 'query-string'; import { toast } from 'react-toastify'; @@ -33,6 +31,7 @@ import { flattenToAppURL, getBlocksFieldname, getBlocksLayoutFieldname, + getLanguageIndependentFields, langmap, normalizeLanguageName, } from '@plone/volto/helpers'; @@ -297,6 +296,16 @@ class Add extends Component { }); } + const lifData = () => { + const data = {}; + if (translationObject) { + getLanguageIndependentFields(this.props.schema).forEach( + (lif) => (data[lif] = translationObject[lif]), + ); + } + return data; + }; + const pageAdd = (
- - { - this.setState({ - formSelected: 'translationObjectForm', - }); - }} - /> - - -
- - - {`${this.props.intl.formatMessage(messages.translateTo, { - lang: translateTo, - })}`} - - - {pageAdd} -
-
- + <> + + + + { + this.setState({ + formSelected: 'translationObjectForm', + }); + }} + /> + + +
+ + + {`${this.props.intl.formatMessage(messages.translateTo, { + lang: translateTo, + })}`} + + + {pageAdd} +
+
+
+ ) : ( pageAdd ); @@ -432,7 +447,6 @@ class Add extends Component { } export default compose( - DragDropContext(HTML5Backend), injectIntl, connect( (state, props) => ({ diff --git a/src/components/manage/Blocks/Block/BlocksForm.test.jsx b/src/components/manage/Blocks/Block/BlocksForm.test.jsx index 738878c7ac..6da6cbb705 100644 --- a/src/components/manage/Blocks/Block/BlocksForm.test.jsx +++ b/src/components/manage/Blocks/Block/BlocksForm.test.jsx @@ -6,6 +6,12 @@ import { render } from '@testing-library/react'; import config from '@plone/volto/registry'; +jest.mock('@plone/volto/helpers/Loadable/Loadable'); +beforeAll( + async () => + await require('@plone/volto/helpers/Loadable/Loadable').__setLoadables(), +); + let mockSerial = 0; jest.mock('uuid', () => { diff --git a/src/components/manage/Blocks/Listing/ListingBody.jsx b/src/components/manage/Blocks/Listing/ListingBody.jsx index 507d32ed9a..7a2391838b 100644 --- a/src/components/manage/Blocks/Listing/ListingBody.jsx +++ b/src/components/manage/Blocks/Listing/ListingBody.jsx @@ -97,6 +97,12 @@ const ListingBody = withQuerystringResults((props) => {
) : (
+ {hasLoaded && ( + + )} diff --git a/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap b/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap index d91cbc9314..352770202f 100644 --- a/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap +++ b/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap @@ -2,6 +2,7 @@ exports[`renders a ListingBody component 1`] = `
+ No results found.
- - - - - - } - > - - - {map( - [ - 'id', - 'sortable_title', - 'EffectiveDate', - 'CreationDate', - 'ModificationDate', - 'portal_type', - ], - (index) => ( - - - - - - {' '} - - - - {' '} - - - - - ), +
+
+ + + + - - - - 0 - ? '#007eb1' - : '#826a6a' - } - size="24px" - /> - } - icon={null} - > - - - + icon={ {' '} - - - + } + > + + + {map( + [ + 'id', + 'sortable_title', + 'EffectiveDate', + 'CreationDate', + 'ModificationDate', + 'portal_type', + ], + (index) => ( + + + + + + {' '} + + + + {' '} + + + + + ), + )} + + + + + 0 + ? '#007eb1' + : '#826a6a' + } size="24px" - />{' '} - - - - - } - iconPosition="left" - className="search" - placeholder={this.props.intl.formatMessage( - messages.filter, - )} - onChange={this.onChangeSelected} - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - }} - /> - - {map(filteredItems, (item) => ( - - {' '} - {this.getFieldById(item, 'title')} - - ))} + } + icon={null} + > + + + + {' '} + + + + {' '} + + + + + } + iconPosition="left" + className="search" + placeholder={this.props.intl.formatMessage( + messages.filter, + )} + onChange={this.onChangeSelected} + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + }} + /> + + {map(filteredItems, (item) => ( + + {' '} + {this.getFieldById(item, 'title')} + + ))} + - - - - + + + + + {map( + this.state.index.order, + (index, order) => + this.state.index.values[index].selected && ( + + ), )} - > - - - {map( - this.state.index.order, - (index, order) => - this.state.index.values[index].selected && ( - - ), - )} - - + + + + + + {this.state.items.map((item, order) => ( + ({ + id: index, + type: this.state.index.values[index].type, + })), + (index) => + this.state.index.values[index.id].selected, + )} + onCut={this.cut} + onCopy={this.copy} + onDelete={this.delete} + onOrderItem={this.onOrderItem} + onMoveToTop={this.onMoveToTop} + onMoveToBottom={this.onMoveToBottom} /> - - - - - {this.state.items.map((item, order) => ( - ({ - id: index, - type: this.state.index.values[index].type, - })), - (index) => - this.state.index.values[index.id].selected, - )} - onCut={this.cut} - onCopy={this.copy} - onDelete={this.delete} - onOrderItem={this.onOrderItem} - onMoveToTop={this.onMoveToTop} - onMoveToBottom={this.onMoveToBottom} - /> - ))} - -
+ ))} + + +
{ + const { DragDropContext } = props.reactDnd; + const HTML5Backend = props.reactDndHtml5Backend.default; + + const DndConnectedContents = React.useMemo( + () => DragDropContext(HTML5Backend)(Contents), + [DragDropContext, HTML5Backend], + ); + + return ; +}; + export const __test__ = compose( injectIntl, - injectLazyLibs(['toastify']), + injectLazyLibs(['toastify', 'reactDnd']), connect( (store, props) => { return { @@ -1817,7 +1832,6 @@ export const __test__ = compose( )(Contents); export default compose( - DragDropContext(HTML5Backend), injectIntl, connect( (store, props) => { @@ -1864,5 +1878,5 @@ export default compose( await dispatch(listActions(getBaseUrl(location.pathname))), }, ]), - injectLazyLibs(['toastify']), -)(Contents); + injectLazyLibs(['toastify', 'reactDnd', 'reactDndHtml5Backend']), +)(DragDropConnector); diff --git a/src/components/manage/Contents/Contents.test.jsx b/src/components/manage/Contents/Contents.test.jsx index 5e8f9f9bf7..48f01c1562 100644 --- a/src/components/manage/Contents/Contents.test.jsx +++ b/src/components/manage/Contents/Contents.test.jsx @@ -24,13 +24,6 @@ jest.mock('./ContentsUploadModal', () => jest.fn(() =>
), ); -jest.mock('moment', () => - jest.fn(() => ({ - format: jest.fn(() => 'Sunday, April 23, 2017 3:38 AM'), - fromNow: jest.fn(() => 'a few seconds ago'), - })), -); - describe('Contents', () => { it('renders a folder contents view component', () => { const store = mockStore({ diff --git a/src/components/manage/Contents/ContentsIndexHeader.jsx b/src/components/manage/Contents/ContentsIndexHeader.jsx index 139caf1447..2425db08a7 100644 --- a/src/components/manage/Contents/ContentsIndexHeader.jsx +++ b/src/components/manage/Contents/ContentsIndexHeader.jsx @@ -5,8 +5,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { DragSource, DropTarget } from 'react-dnd'; import { injectIntl } from 'react-intl'; +import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; const widthValues = [ 'one', @@ -69,39 +69,51 @@ ContentsIndexHeaderComponent.propTypes = { onOrderIndex: PropTypes.func.isRequired, }; -export default DropTarget( - 'index', - { - hover(props, monitor) { - const dragOrder = monitor.getItem().order; - const hoverOrder = props.order; +const DragDropConnector = (props) => { + const { DropTarget, DragSource } = props.reactDnd; - if (dragOrder === hoverOrder) { - return; - } + const DndConnectedContentsIndexHeader = React.useMemo( + () => + DropTarget( + 'index', + { + hover(props, monitor) { + const dragOrder = monitor.getItem().order; + const hoverOrder = props.order; - props.onOrderIndex(dragOrder, hoverOrder - dragOrder); + if (dragOrder === hoverOrder) { + return; + } - monitor.getItem().order = hoverOrder; - }, - }, - (connect) => ({ - connectDropTarget: connect.dropTarget(), - }), -)( - DragSource( - 'index', - { - beginDrag(props) { - return { - id: props.label, - order: props.order, - }; - }, - }, - (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - isDragging: monitor.isDragging(), - }), - )(injectIntl(ContentsIndexHeaderComponent)), -); + props.onOrderIndex(dragOrder, hoverOrder - dragOrder); + + monitor.getItem().order = hoverOrder; + }, + }, + (connect) => ({ + connectDropTarget: connect.dropTarget(), + }), + )( + DragSource( + 'index', + { + beginDrag(props) { + return { + id: props.label, + order: props.order, + }; + }, + }, + (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + }), + )(injectIntl(ContentsIndexHeaderComponent)), + ), + [DragSource, DropTarget], + ); + + return ; +}; + +export default injectLazyLibs('reactDnd')(DragDropConnector); diff --git a/src/components/manage/Contents/ContentsItem.jsx b/src/components/manage/Contents/ContentsItem.jsx index d71cf2fa04..d77bb52d2b 100644 --- a/src/components/manage/Contents/ContentsItem.jsx +++ b/src/components/manage/Contents/ContentsItem.jsx @@ -8,10 +8,8 @@ import { Button, Dropdown, Table } from 'semantic-ui-react'; import { Link } from 'react-router-dom'; import PropTypes from 'prop-types'; import { map } from 'lodash'; -import moment from 'moment'; -import { DragSource, DropTarget } from 'react-dnd'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; -import { Icon, Circle } from '@plone/volto/components'; +import { Circle, FormattedDate, Icon } from '@plone/volto/components'; import { getContentIcon } from '@plone/volto/helpers'; import moreSVG from '@plone/volto/icons/more.svg'; import checkboxUncheckedSVG from '@plone/volto/icons/checkbox-unchecked.svg'; @@ -26,6 +24,8 @@ import editingSVG from '@plone/volto/icons/editing.svg'; import dragSVG from '@plone/volto/icons/drag.svg'; import cx from 'classnames'; +import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; + const messages = defineMessages({ private: { id: 'private', @@ -47,6 +47,10 @@ const messages = defineMessages({ id: 'no workflow state', defaultMessage: 'No workflow state', }, + none: { + id: 'None', + defaultMessage: 'None', + }, }); function getColor(string) { @@ -186,21 +190,13 @@ export const ContentsItemComponent = ({
)} {index.type === 'date' && ( - - ) - } - > + <> {item[index.id] !== 'None' ? ( - moment(item[index.id]).format('L') + ) : ( - + intl.formatMessage(messages.none) )} - + )} {index.type === 'array' && ( {item[index.id]?.join(', ')} @@ -313,55 +309,67 @@ ContentsItemComponent.propTypes = { onOrderItem: PropTypes.func.isRequired, }; -export default DropTarget( - 'item', - { - hover(props, monitor) { - const id = monitor.getItem().id; - const dragOrder = monitor.getItem().order; - const hoverOrder = props.order; +const DragDropConnector = (props) => { + const { DropTarget, DragSource } = props.reactDnd; - if (dragOrder === hoverOrder) { - return; - } + const DndConnectedContentsItem = React.useMemo( + () => + DropTarget( + 'item', + { + hover(props, monitor) { + const id = monitor.getItem().id; + const dragOrder = monitor.getItem().order; + const hoverOrder = props.order; - props.onOrderItem(id, dragOrder, hoverOrder - dragOrder, false); + if (dragOrder === hoverOrder) { + return; + } - monitor.getItem().order = hoverOrder; - }, - drop(props, monitor) { - const id = monitor.getItem().id; - const dragOrder = monitor.getItem().startOrder; - const dropOrder = props.order; + props.onOrderItem(id, dragOrder, hoverOrder - dragOrder, false); - if (dragOrder === dropOrder) { - return; - } + monitor.getItem().order = hoverOrder; + }, + drop(props, monitor) { + const id = monitor.getItem().id; + const dragOrder = monitor.getItem().startOrder; + const dropOrder = props.order; - props.onOrderItem(id, dragOrder, dropOrder - dragOrder, true); + if (dragOrder === dropOrder) { + return; + } - monitor.getItem().order = dropOrder; - }, - }, - (connect) => ({ - connectDropTarget: connect.dropTarget(), - }), -)( - DragSource( - 'item', - { - beginDrag(props) { - return { - id: props.item['@id'], - order: props.order, - startOrder: props.order, - }; - }, - }, - (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - connectDragPreview: connect.dragPreview(), - isDragging: monitor.isDragging(), - }), - )(ContentsItemComponent), -); + props.onOrderItem(id, dragOrder, dropOrder - dragOrder, true); + + monitor.getItem().order = dropOrder; + }, + }, + (connect) => ({ + connectDropTarget: connect.dropTarget(), + }), + )( + DragSource( + 'item', + { + beginDrag(props) { + return { + id: props.item['@id'], + order: props.order, + startOrder: props.order, + }; + }, + }, + (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + connectDragPreview: connect.dragPreview(), + isDragging: monitor.isDragging(), + }), + )(ContentsItemComponent), + ), + [DragSource, DropTarget], + ); + + return ; +}; + +export default injectLazyLibs('reactDnd')(DragDropConnector); diff --git a/src/components/manage/Contents/ContentsUploadModal.jsx b/src/components/manage/Contents/ContentsUploadModal.jsx index 13d0a50b2f..52e43a3ecb 100644 --- a/src/components/manage/Contents/ContentsUploadModal.jsx +++ b/src/components/manage/Contents/ContentsUploadModal.jsx @@ -20,10 +20,10 @@ import { } from 'semantic-ui-react'; import loadable from '@loadable/component'; import { concat, filter, map } from 'lodash'; -import moment from 'moment'; import filesize from 'filesize'; import { readAsDataURL } from 'promise-file-reader'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { FormattedRelativeDate } from '@plone/volto/components'; import { createContent } from '@plone/volto/actions'; const Dropzone = loadable(() => import('react-dropzone')); @@ -180,8 +180,6 @@ class ContentsUploadModal extends Component { * @returns {string} Markup for the component. */ render() { - moment.locale(this.props.intl.locale); - return ( this.props.open && ( @@ -269,7 +267,7 @@ class ContentsUploadModal extends Component { {file.name} - {moment(file.lastModifiedDate).fromNow()} + {filesize(file.size, { round: 0 })} diff --git a/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap b/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap index 6398be2bf9..05922f560d 100644 --- a/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap +++ b/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap @@ -368,16 +368,11 @@ exports[`Contents renders a folder contents view component 1`] = ` onChange={[Function]} onClick={[Function]} onFocus={[Function]} + onKeyDown={[Function]} onMouseDown={[Function]} role="listbox" tabIndex={0} > -
- - - - - - - - - - -
-
- -
-
- Rearrange items by… -
-
- - ID + } + viewBox="" + xmlns="" + /> +
-
- +
+ - - Ascending -
+ } + viewBox="" + xmlns="" + /> + ID
- + - - Descending + viewBox="" + xmlns="" + /> + + Ascending +
+
+ + + Descending +
-
-
- - Title
-
- - - Ascending -
+ } + viewBox="" + xmlns="" + /> + Title
- + - - Descending + viewBox="" + xmlns="" + /> + + Ascending +
+
+ + + Descending +
-
-
- - Publication date
-
- - - Ascending -
+ } + viewBox="" + xmlns="" + /> + Publication date
- + - - Descending + viewBox="" + xmlns="" + /> + + Ascending +
+
+ + + Descending +
-
-
- - Created on
-
- - - Ascending -
+ } + viewBox="" + xmlns="" + /> + Created on
- + - - Descending + viewBox="" + xmlns="" + /> + + Ascending +
+
+ + + Descending +
-
-
- - Last modified
-
- - - Ascending -
+ } + viewBox="" + xmlns="" + /> + Last modified
- + - - Descending + viewBox="" + xmlns="" + /> + + Ascending +
+
+ + + Descending +
-
-
- - Type
-
- - - Ascending -
+ } + viewBox="" + xmlns="" + /> + Type
- + - - Descending + viewBox="" + xmlns="" + /> + + Ascending +
+
+ + + Descending +
- -
-
+
-
-
- Select… -
-
- - - All -
+ } + viewBox="" + xmlns="" + />
- + Select… +
+
+ - - None -
-
-
- 0 selected -
-
- + + All +
+
+ + + None +
+
- + 0 selected +
+
+ + +
+
-
-
-
- Title - - Actions -
+ + + Title + + + Actions + + + + + +
diff --git a/src/components/manage/Contents/__snapshots__/ContentsItem.test.jsx.snap b/src/components/manage/Contents/__snapshots__/ContentsItem.test.jsx.snap index 7346376e5e..e7fc79f7b3 100644 --- a/src/components/manage/Contents/__snapshots__/ContentsItem.test.jsx.snap +++ b/src/components/manage/Contents/__snapshots__/ContentsItem.test.jsx.snap @@ -121,16 +121,11 @@ exports[`ContentsItem renders a contents item component 1`] = ` onChange={[Function]} onClick={[Function]} onFocus={[Function]} + onKeyDown={[Function]} onMouseDown={[Function]} role="listbox" tabIndex={0} > -
-