From 0baae00bdc796c4b5733b83a27245830b3d0a5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 13 Nov 2022 16:10:19 +0100 Subject: [PATCH 001/326] Load core add-ons configuration as any other add-on. (#3876) * Load core add-ons configuration as any other add-on. * Fix problems with tests and packages folder add-on loaders --- CHANGELOG.md | 2 ++ ...test.js => addon-registry-project.test.js} | 20 ++++++++++++------- __tests__/addon-registry-volto.test.js | 11 ++++++++++ .../node_modules/@plone/volto/package.json | 13 ------------ addon-registry.js | 12 +++++++---- src/config/index.js | 3 +-- 6 files changed, 35 insertions(+), 26 deletions(-) rename __tests__/{addon-registry.test.js => addon-registry-project.test.js} (95%) create mode 100644 __tests__/addon-registry-volto.test.js delete mode 100644 __tests__/fixtures/test-volto-project/node_modules/@plone/volto/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c23ee6892..b3b30b7752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugfix +- Load core add-ons configuration as any other add-on. @sneridagh + ### Internal ### Documentation diff --git a/__tests__/addon-registry.test.js b/__tests__/addon-registry-project.test.js similarity index 95% rename from __tests__/addon-registry.test.js rename to __tests__/addon-registry-project.test.js index dbf62b0034..c623acc5b8 100644 --- a/__tests__/addon-registry.test.js +++ b/__tests__/addon-registry-project.test.js @@ -5,13 +5,19 @@ const { getAddonsLoaderChain, } = AddonConfigurationRegistry; -describe('AddonConfigurationRegistry', () => { - it('works in Volto', () => { - const base = path.join(__dirname, '..'); - const reg = new AddonConfigurationRegistry(base); - expect(reg.projectRootPath).toStrictEqual(base); - expect(reg.addonNames).toStrictEqual(['@plone/volto-slate']); - }); +describe('AddonConfigurationRegistry - Project', () => { + jest.mock( + `${path.join( + __dirname, + 'fixtures', + 'test-volto-project', + )}/node_modules/@plone/volto/package.json`, + () => ({ + // TODO: mock the packages folder inside the mocked @plone/volto to work with resolves + packagesFolderAddons: {}, + }), + { virtual: true }, + ); it('works in a mock project directory', () => { const base = path.join(__dirname, 'fixtures', 'test-volto-project'); diff --git a/__tests__/addon-registry-volto.test.js b/__tests__/addon-registry-volto.test.js new file mode 100644 index 0000000000..1f6c6a7cff --- /dev/null +++ b/__tests__/addon-registry-volto.test.js @@ -0,0 +1,11 @@ +const path = require('path'); +const AddonConfigurationRegistry = require('../addon-registry'); + +describe('AddonConfigurationRegistry - Volto', () => { + it('works in Volto', () => { + const base = path.join(__dirname, '..'); + const reg = new AddonConfigurationRegistry(base); + expect(reg.projectRootPath).toStrictEqual(base); + expect(reg.addonNames).toStrictEqual(['@plone/volto-slate']); + }); +}); diff --git a/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/package.json b/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/package.json deleted file mode 100644 index 6278c74df3..0000000000 --- a/__tests__/fixtures/test-volto-project/node_modules/@plone/volto/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@plone/volto", - "version": "0.0.0", - "main": "src/index.js", - "packagesFolderAddons": { - "volto-slate": { - "package": "@plone/volto-slate" - }, - "scripts": { - "package": "@plone/scripts" - } - } -} diff --git a/addon-registry.js b/addon-registry.js index eeba6b3257..3dfea21f45 100644 --- a/addon-registry.js +++ b/addon-registry.js @@ -108,10 +108,6 @@ class AddonConfigurationRegistry { } else { this.voltoConfigJS = []; } - this.resultantMergedAddons = [ - ...(packageJson.addons || []), - ...(this.voltoConfigJS.addons || []), - ]; this.projectRootPath = projectRootPath; this.voltoPath = @@ -125,6 +121,11 @@ class AddonConfigurationRegistry { : require(`${getPackageBasePath(this.voltoPath)}/package.json`) .packagesFolderAddons || {}; + this.resultantMergedAddons = [ + ...(packageJson.addons || []), + ...(this.voltoConfigJS.addons || []), + ]; + this.addonNames = this.resultantMergedAddons.map((s) => s.split(':')[0]); this.packages = {}; this.customizations = new Map(); @@ -135,6 +136,9 @@ class AddonConfigurationRegistry { this.dependencyGraph = buildDependencyGraph( [ + ...(Object.keys(this.packagesFolderAddons).map( + (key) => this.packagesFolderAddons[key].package, + ) || []), ...this.resultantMergedAddons, ...(process.env.ADDONS ? process.env.ADDONS.split(';') : []), ], diff --git a/src/config/index.js b/src/config/index.js index 90f14059bc..7e852cf386 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -27,7 +27,6 @@ import { contentIcons } from './ContentIcons'; import { controlPanelsIcons } from './ControlPanels'; import { richtextEditorSettings, richtextViewSettings } from './RichTextEditor'; -import applySlateConfiguration from '@plone/volto-slate'; import applyAddonConfiguration, { addonsInfo } from 'load-volto-addons'; @@ -196,4 +195,4 @@ ConfigRegistry.addonReducers = config.addonReducers; ConfigRegistry.appExtras = config.appExtras; ConfigRegistry.components = config.components; -applyAddonConfiguration(applySlateConfiguration(ConfigRegistry)); +applyAddonConfiguration(ConfigRegistry); From 0514a0de47d25098884fa8cc9961ac363bd6388e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 14 Nov 2022 11:52:13 +0100 Subject: [PATCH 002/326] Complete yarn 3 upgrade for projects (#3879) * Complete yarn 3 upgrade for projects * Apply Steve's Suggestions --- CHANGELOG.md | 1 + docs/source/upgrade-guide/index.md | 119 +++++++++++------- packages/generator-volto/CHANGELOG.md | 2 + .../generators/app/templates/Makefile | 27 +++- .../generators/app/templates/package.json.tpl | 5 +- 5 files changed, 100 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b30b7752..aca1e2b981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Documentation - Add missing pieces of the upgrade to use yarn 3 for projects @sneridagh +- Complete docs about the yarn 3 upgrade @sneridagh ## 16.0.0-alpha.49 (2022-11-11) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 99be41d2cf..2d475fa80b 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -226,65 +226,96 @@ We are updating Volto to be able to use it, however some changes have to be made 1. Enable yarn 3 in your projects, adding `yarnrc.yml`: -```yml -defaultSemverRangePrefix: "" + ```yml + defaultSemverRangePrefix: "" -nodeLinker: node-modules -``` + nodeLinker: node-modules + ``` -Then, if you are using Node >=16.10 run: + Then, if you are using Node >=16.10 run: -```shell -corepack enable -yarn set version 3.2.3 -``` + ```shell + corepack enable + yarn set version 3.2.3 + ``` -Corepack isn't included with Node.js in versions before 16.10. -To address that, run: + Corepack isn't included with Node.js in versions before 16.10. + To address that, run: -```shell -npm i -g corepack -``` + ```shell + npm i -g corepack + ``` -```{note} -It is highly recommended that you use latest Node 16. -``` + ```{note} + It is highly recommended that you use latest Node 16. + ``` 2. Change your root project `Makefile` to include these commands: -```diff -+.PHONY: preinstall -+preinstall: -+ if [ -f $$(pwd)/mrs.developer.json ]; then if [ -f $$(pwd)/node_modules/.bin/missdev ]; then yarn develop; else yarn develop:npx; fi; fi -+ -+.PHONY: omelette -+omelette: -+ if [ ! -d omelette ]; then ln -sf node_modules/@plone/volto omelette; fi -``` + ```diff + +.PHONY: install + +install: ## Install the frontend + + @echo "Install frontend" + + $(MAKE) omelette + + $(MAKE) preinstall + + yarn install + + + +.PHONY: preinstall + +preinstall: ## Preinstall task, checks if missdev (mrs-developer) is present and runs it + + if [ -f $$(pwd)/mrs.developer.json ]; then make develop; fi + + + + +.PHONY: develop + +develop: ## Runs missdev in the local project (mrs.developer.json should be present) + + npx -p mrs-developer missdev --config=jsconfig.json --output=addons --fetch-https + + + +.PHONY: omelette + +omelette: ## Creates the omelette folder that contains a link to the installed version of Volto (a softlink pointing to node_modules/@plone/volto) + + if [ ! -d omelette ]; then ln -sf node_modules/@plone/volto omelette; fi + + + +.PHONY: patches + +patches: + + /bin/bash patches/patchit.sh > /dev/null 2>&1 ||true + ``` + + You can copy the file over to your project if you have not made any amendment to it. 3. Change your `package.json` scripts section: -```diff - "version": "1.0.0", - "scripts": { - "start": "razzle start", -- "preinstall": "if [ -f $(pwd)/mrs.developer.json ]; then if [ -f $(pwd)/node_modules/.bin/missdev ]; then yarn develop; else yarn develop:npx; fi; fi", -+ "preinstall": "make preinstall", - "postinstall": "yarn omelette && yarn patches", -- "omelette": "if [ ! -d omelette ]; then ln -sf node_modules/@plone/volto omelette; fi", -+ "omelette": "make omelette", - "patches": "/bin/bash patches/patchit.sh > /dev/null 2>&1 ||true", -``` - -Yarn 3 no longer support inline bash scripts in the `scripts` section. -We need to move them to the `Makefile` and update the calls. + ```diff + "version": "1.0.0", + "scripts": { + "start": "razzle start", + - "preinstall": "if [ -f $(pwd)/mrs.developer.json ]; then if [ -f $(pwd)/node_modules/.bin/missdev ]; then yarn develop; else yarn develop:npx; fi; fi", + - "omelette": "if [ ! -d omelette ]; then ln -sf node_modules/@plone/volto omelette; fi", + - "patches": "/bin/bash patches/patchit.sh > /dev/null 2>&1 ||true", + - "postinstall": "yarn omelette && yarn patches", + + "postinstall": "make omelette && make patches", + - "patches": "/bin/bash patches/patchit.sh > /dev/null 2>&1 ||true", + ``` + + Yarn 3 no longer supports inline bash scripts in the `scripts` section. + It does not support the `preinstall` lifecycle state, so we cannot trigger things while we run `yarn install` or just `yarn`. + As a result, we moved all the commands related to the `preinstall` actions into `Makefile`, then updated the calls in both files as shown above. + + ```{important} + After making the changes in this step, we will have to modify our development workflow, especially if we use actions that happened during the `preinstall` state. + The most relevant one is updating the code that is managed by `mrs-developer`. + If you follow the code above, you'll need to call these `Makefile` commands before each `yarn install`. + It's recommended to use the `make install` command instead of just `yarn` or `yarn install`. + + ```shell + make install + ``` + + Remember to update your CI scripts accordingly. 4. It doesn't allow to use commands not declared as direct dependencies, so in your projects you should add `razzle` as a dependency: -```diff -devDependencies: { -+ "razzle": "4.2.17", -``` + ```diff + devDependencies: { + + "razzle": "4.2.17", + ``` 5. Replace your project `yarn.lock` for the Volto's one, then run `yarn` again. diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 7b872523ca..3bb3191880 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -6,6 +6,8 @@ ### Feature +- Last bit and pieces of the yarn 3 upgrade @sneridagh + ### Bugfix ### Internal diff --git a/packages/generator-volto/generators/app/templates/Makefile b/packages/generator-volto/generators/app/templates/Makefile index 80a829da92..fe9f4aff61 100644 --- a/packages/generator-volto/generators/app/templates/Makefile +++ b/packages/generator-volto/generators/app/templates/Makefile @@ -33,6 +33,10 @@ YELLOW=`tput setaf 3` .PHONY: all all: project +.PHONY: help +help: ## Show this help. + @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)" + .PHONY: start-test-backend start-test-backend: ## Start Test Plone Backend @echo "$(GREEN)==> Start Test Plone Backend$(RESET)" @@ -45,14 +49,25 @@ start-backend-docker: ## Starts a Docker-based backend @echo "$(GREEN)==> Start Docker-based Plone Backend$(RESET)" docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE) -.PHONY: help -help: ## Show this help. - @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)" +.PHONY: install +install: ## Install the frontend + @echo "Install frontend" + $(MAKE) omelette + $(MAKE) preinstall + yarn install .PHONY: preinstall -preinstall: - if [ -f $$(pwd)/mrs.developer.json ]; then if [ -f $$(pwd)/node_modules/.bin/missdev ]; then yarn develop; else yarn develop:npx; fi; fi +preinstall: ## Preinstall task, checks if missdev (mrs-developer) is present and runs it + if [ -f $$(pwd)/mrs.developer.json ]; then make develop; fi + +.PHONY: develop +develop: ## Runs missdev in the local project (mrs.developer.json should be present) + npx -p mrs-developer missdev --config=jsconfig.json --output=addons --fetch-https .PHONY: omelette -omelette: +omelette: ## Creates the omelette folder that contains a link to the installed version of Volto (a softlink pointing to node_modules/@plone/volto) if [ ! -d omelette ]; then ln -sf node_modules/@plone/volto omelette; fi + +.PHONY: patches +patches: + /bin/bash patches/patchit.sh > /dev/null 2>&1 ||true diff --git a/packages/generator-volto/generators/app/templates/package.json.tpl b/packages/generator-volto/generators/app/templates/package.json.tpl index dfc55411a7..44d8084609 100644 --- a/packages/generator-volto/generators/app/templates/package.json.tpl +++ b/packages/generator-volto/generators/app/templates/package.json.tpl @@ -5,10 +5,7 @@ "version": "1.0.0", "scripts": { "start": "razzle start", - "preinstall": "make preinstall", - "postinstall": "yarn omelette && yarn patches", - "omelette": "make omelette", - "patches": "/bin/bash patches/patchit.sh > /dev/null 2>&1 ||true", + "postinstall": "make omelette && make patches", "build": "razzle build --noninteractive", "lint": "./node_modules/eslint/bin/eslint.js --max-warnings=0 'src/**/*.{js,jsx}'", "lint:fix": "./node_modules/eslint/bin/eslint.js --max-warnings=0 --fix 'src/**/*.{js,jsx}'", From 6c3a20961d5024d3aa24019e830e73a1f04c5637 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Nov 2022 11:54:05 +0100 Subject: [PATCH 003/326] Prepare for release --- packages/generator-volto/CHANGELOG.md | 6 ------ packages/generator-volto/generators/app/templates/README.md | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 3bb3191880..21c4cc5f88 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -2,16 +2,10 @@ ## 6.0.0 (unreleased) -### Breaking - ### Feature - Last bit and pieces of the yarn 3 upgrade @sneridagh -### Bugfix - -### Internal - ## 6.0.0-alpha.0 (2022-11-12) ### Breaking diff --git a/packages/generator-volto/generators/app/templates/README.md b/packages/generator-volto/generators/app/templates/README.md index e5d9aff411..5776d0375f 100644 --- a/packages/generator-volto/generators/app/templates/README.md +++ b/packages/generator-volto/generators/app/templates/README.md @@ -44,7 +44,7 @@ for developing multiple packages at the same time. mrs-developer should work with this project by running the configured shortcut script: ```bash -yarn develop +make develop ``` Volto's latest razzle config will pay attention to your tsconfig.json (or jsconfig.json) file for any customizations. From e3824f9ea02f0ce6c1cc0151f9f74e9152f5c901 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Nov 2022 11:54:31 +0100 Subject: [PATCH 004/326] Release generate-volto 6.0.0-alpha.1 --- packages/generator-volto/CHANGELOG.md | 2 +- packages/generator-volto/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 21c4cc5f88..198a4d75e9 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 6.0.0 (unreleased) +## 6.0.0-alpha.1 (2022-11-14) ### Feature diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index cdd33f4765..7ab993da85 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "6.0.0-alpha.0", + "version": "6.0.0-alpha.1", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" From a328e9287cd50f6a991aeca4c904894e16226e99 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Nov 2022 11:54:39 +0100 Subject: [PATCH 005/326] Back to development (generator-volto) --- packages/generator-volto/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 198a4d75e9..cb53e9e1f5 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## 6.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + ## 6.0.0-alpha.1 (2022-11-14) ### Feature From 329bddff86edfa826152077ac7737b6cefbcb1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 14 Nov 2022 15:47:01 +0100 Subject: [PATCH 006/326] Fix `FormValidation` error object, use field `id` instead of field `title` (#3878) * Fix FormValidation, do not use the title for the key in the error object, use the id instead * Changelog --- CHANGELOG.md | 1 + src/components/manage/Form/Form.jsx | 6 +++++- src/helpers/FormValidation/FormValidation.js | 6 ++---- src/helpers/FormValidation/FormValidation.test.js | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aca1e2b981..29f0e48480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Bugfix - Load core add-ons configuration as any other add-on. @sneridagh +- Fix `FormValidation` error object, use field `id` instead of field `title` @sneridagh ### Internal diff --git a/src/components/manage/Form/Form.jsx b/src/components/manage/Form/Form.jsx index 9ba700d770..5356ca57d2 100644 --- a/src/components/manage/Form/Form.jsx +++ b/src/components/manage/Form/Form.jsx @@ -441,7 +441,11 @@ class Form extends Component { () => { Object.keys(errors).forEach((err) => toast.error( - , + , ), ); }, diff --git a/src/helpers/FormValidation/FormValidation.js b/src/helpers/FormValidation/FormValidation.js index 9477b1db27..d708c53e5d 100644 --- a/src/helpers/FormValidation/FormValidation.js +++ b/src/helpers/FormValidation/FormValidation.js @@ -210,10 +210,8 @@ const validateRequiredFields = ( !schema.properties[requiredField].readonly && isEmpty ) { - const requiredFieldName = - schema.properties[requiredField].title || requiredField; - errors[requiredFieldName] = []; - errors[requiredFieldName].push(formatMessage(messages.required)); + errors[requiredField] = []; + errors[requiredField].push(formatMessage(messages.required)); } }); diff --git a/src/helpers/FormValidation/FormValidation.test.js b/src/helpers/FormValidation/FormValidation.test.js index 2bad474662..56da3a43a3 100644 --- a/src/helpers/FormValidation/FormValidation.test.js +++ b/src/helpers/FormValidation/FormValidation.test.js @@ -61,7 +61,7 @@ describe('FormValidation', () => { formatMessage, }), ).toEqual({ - Username: [messages.required.defaultMessage], + username: [messages.required.defaultMessage], }); }); From 67dcc0a52ade4b951ce51eac5534a3c3d8053e42 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 14 Nov 2022 23:07:41 +0100 Subject: [PATCH 007/326] Fix indentation in Volto 13 upgrade guide --- docs/source/upgrade-guide/index.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 2d475fa80b..341093ff34 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -770,20 +770,20 @@ The `getVocabulary` action has changed API. Before, it used separate positional ## Upgrading to Volto 13.x.x -## Deprecating NodeJS 10 +### Deprecating NodeJS 10 Since April 30th, 2021 NodeJS 10 is out of Long Term Support by the NodeJS community, so we are deprecating it in Volto 13. Please update your projects to a NodeJS LTS version (12 or 14 at the moment of this writing). -## Seamless mode is the default in development mode +### Seamless mode is the default in development mode Not really a breaking change, but it's worth noting it. By default, Volto 13 in development mode uses the internal proxy in seamless mode otherwise configured differently. To learn more about the seamless mode read: {doc}`../deploying/seamless-mode` and {doc}`../configuration/zero-config-builds`. -## Refactored Listing block using schemas and ObjectWidget +### Refactored Listing block using schemas and ObjectWidget The Listing block has been heavily refactored using schema forms and `BlockDataForm` as well as the other new internal artifacts to leverage blocks variations and extensions at the same time @@ -797,7 +797,7 @@ The advantage of this is that now you can use the `QuerystringWidget` with schem data forms in a reusable way in your custom blocks. See the Listing block code for further references. -### Migrate your existing listing blocks +#### Migrate your existing listing blocks **(Updated: 2021/06/12)** If you have an existing Volto installation and you are using listing blocks, you must run an upgrade step in order to match the new listing @@ -880,7 +880,7 @@ When an official integration package exists, these upgrade steps in the backend will be provided in there. ``` -### Update your custom variations (templates) in your project listing blocks +#### Update your custom variations (templates) in your project listing blocks In the case that you have custom templates for your listing blocks in your projects, it's required that you update the definitions to match the new core variations syntax. @@ -910,7 +910,7 @@ To this: ] ``` -## Control panel icons are now SVG based instead of font based +### Control panel icons are now SVG based instead of font based It was long due, the control panel overview route `/controlpanel` is now using SVG icons from the Pastanaga icon set, instead of the deprecated font ones. If you have customized @@ -924,13 +924,13 @@ import config from '@plone/volto/registry' config.settings.controlPanelsIcons.mynewcontrolpanelid = myfancyiconSVG; ``` -## Login form UI and accessibility updated +### Login form UI and accessibility updated Not really a breaking change, but it's worth to note that we changed the look and feel of the login form and improved its usability and accessibility. Another move towards the new Quanta look and feel. -## Changes in the Table block feature set and messages +### Changes in the Table block feature set and messages The "inverted" option in Table Block was removed since it was useless with the current CSS set. Better naming of options and labels in table block (English). Updating the i18n From 54cf5018eae14f36bca8a45c4e5d4afd354c9663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Andrei?= Date: Mon, 14 Nov 2022 19:39:14 -0300 Subject: [PATCH 008/326] Brazilian Portuguese translation updated --- CHANGELOG.md | 2 + locales/pt_BR/LC_MESSAGES/volto.po | 336 ++++++++++++++--------------- 2 files changed, 170 insertions(+), 168 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29f0e48480..4b62e3560d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Feature +- Brazilian Portuguese translation updated @ericof + ### Bugfix - Load core add-ons configuration as any other add-on. @sneridagh diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index 8d588cf712..30a03d5bb7 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Plone\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"PO-Revision-Date: 2022-07-26 12:55-0300\n" +"PO-Revision-Date: 2022-11-14 19:36-0300\n" "Last-Translator: Érico Andrei \n" "Language: pt_BR\n" "Language-Team: Portuguese (https://www.transifex.com/plone/teams/14552/pt/)\n" @@ -19,7 +19,7 @@ msgstr "" "Language-Name: Português do Brasil\n" "Preferred-Encodings: utf-8\n" "Domain: volto\n" -"X-Generator: Poedit 3.1.1\n" +"X-Generator: Poedit 3.2\n" #: components/manage/Blocks/HTML/Edit # defaultMessage:

Add some HTML here

@@ -44,12 +44,12 @@ msgstr "Ação" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action changed msgid "Action changed" -msgstr "" +msgstr "Ação alterada" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action: msgid "Action: " -msgstr "" +msgstr "Ação: " #: components/manage/Actions/Actions #: components/manage/Contents/Contents @@ -69,12 +69,12 @@ msgstr "Ativar e desativar" #: components/manage/Rules/Rules # defaultMessage: Active msgid "Active" -msgstr "" +msgstr "Ativa" #: components/manage/Rules/Rules # defaultMessage: Active content rules in this Page msgid "Active content rules in this Page" -msgstr "" +msgstr "Regras de conteúdo ativas nesta página" #: components/manage/Aliases/Aliases #: components/manage/Controlpanels/Aliases @@ -106,12 +106,12 @@ msgstr "Adicionar conteúdo" #: components/manage/Controlpanels/Rules/AddRule # defaultMessage: Add Content Rule msgid "Add Content Rule" -msgstr "" +msgstr "Adicionar regra de conteúdo" #: components/manage/Controlpanels/Rules/AddRule # defaultMessage: Add Rule msgid "Add Rule" -msgstr "" +msgstr "Adicionar regra" #: components/manage/Toolbar/Types # defaultMessage: Add Translation… @@ -131,12 +131,12 @@ msgstr "Adicionar uma descrição…" #: components/manage/Aliases/Aliases # defaultMessage: Add a new alternative url msgid "Add a new alternative url" -msgstr "" +msgstr "Adicionar nova url alternativa" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action added msgid "Add action" -msgstr "" +msgstr "Adicionar ação" #: components/manage/BlockChooser/BlockChooserButton # defaultMessage: Add block @@ -151,12 +151,12 @@ msgstr "Adicionar bloco…" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition added msgid "Add condition" -msgstr "" +msgstr "Adicionar condição" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Add content rule msgid "Add content rule" -msgstr "" +msgstr "Adicionar regra de conteúdo" #: components/manage/Widgets/QueryWidget # defaultMessage: Add criteria @@ -241,7 +241,7 @@ msgstr "Configurações dos complementos" #: components/manage/Rules/Rules # defaultMessage: Added msgid "Added" -msgstr "" +msgstr "Adicionada" #: components/manage/Widgets/RecurrenceWidget/Occurences # defaultMessage: Additional date @@ -251,48 +251,48 @@ msgstr "Outra data" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon could not be installed msgid "Addon could not be installed" -msgstr "" +msgstr "Complemento não pode ser instalado" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon could not be uninstalled msgid "Addon could not be uninstalled" -msgstr "" +msgstr "Complemento não pode ser desinstalado" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon could not be upgraded msgid "Addon could not be upgraded" -msgstr "" +msgstr "Complemento não pode ser atualizado" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon installed succesfuly msgid "Addon installed succesfuly" -msgstr "" +msgstr "Complemento instalado com sucesso" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon uninstalled succesfuly msgid "Addon uninstalled succesfuly" -msgstr "" +msgstr "Complemento desinstalado com sucesso" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon upgraded succesfuly msgid "Addon upgraded succesfuly" -msgstr "" +msgstr "Complemento atualizado com sucesso" #: config/Views # defaultMessage: Album view msgid "Album view" -msgstr "" +msgstr "Visão de álbum" #: components/manage/Controlpanels/Aliases # defaultMessage: Alias msgid "Alias" -msgstr "" +msgstr "Alternativa" #: components/manage/Aliases/Aliases #: components/manage/Controlpanels/Aliases # defaultMessage: Alias has been added msgid "Alias has been added" -msgstr "" +msgstr "Alternativa foi adicionada" #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar @@ -310,12 +310,12 @@ msgstr "Todos" #: config/Views # defaultMessage: All content msgid "All content" -msgstr "" +msgstr "Todo o conteúdo" #: components/manage/Controlpanels/Aliases # defaultMessage: All existing alternative urls for this site msgid "All existing alternative urls for this site" -msgstr "" +msgstr "Todas as urls alternativas para este site" #: components/theme/Search/Search # defaultMessage: Alphabetically @@ -332,43 +332,43 @@ msgstr "Descrição Alt" #: components/manage/Blocks/Image/schema # defaultMessage: Leave empty if the image is purely decorative. msgid "Alt text hint" -msgstr "Deixe em branco se a imagem for puramente decorativa." +msgstr "Deixe em branco se a imagem for puramente decorativa" #: components/manage/Blocks/Image/schema # defaultMessage: Describe the purpose of the image. msgid "Alt text hint link text" -msgstr "Descreve o propósito da imagem." +msgstr "Descreve o propósito da imagem" #: components/manage/Controlpanels/Aliases # defaultMessage: Alternative url path (Required) msgid "Alternative url path (Required)" -msgstr "" +msgstr "Caminho url alternativo (Obrigatório)" #: components/manage/Aliases/Aliases #: components/manage/Controlpanels/Aliases # defaultMessage: Alternative url path must start with a slash. msgid "Alternative url path must start with a slash." -msgstr "" +msgstr "Caminho url alternativo deve iniciar com uma barra." #: components/manage/Controlpanels/Aliases # defaultMessage: Alternative url path → target url path (date and time of creation, manually created yes/no) msgid "Alternative url path → target url path (date and time of creation, manually created yes/no)" -msgstr "" +msgstr "Caminho url alternativo → caminho da url de destino (data e hora da criação, criado manualmente sim/não)" #: components/manage/Rules/Rules # defaultMessage: Applied to subfolders msgid "Applied to subfolders" -msgstr "" +msgstr "Aplicada em subpastas" #: components/manage/Rules/Rules # defaultMessage: Applies to subfolders? msgid "Applies to subfolders?" -msgstr "" +msgstr "Aplicar em subpastas?" #: components/manage/Rules/Rules # defaultMessage: Apply to subfolders msgid "Apply to subfolders" -msgstr "" +msgstr "Aplicar em subpastas" #: components/manage/Toolbar/More # defaultMessage: Apply working copy @@ -394,7 +394,7 @@ msgstr "Ascendente" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Assignments msgid "Assignments" -msgstr "" +msgstr "Atribuições" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Available @@ -404,7 +404,7 @@ msgstr "Disponível" #: components/manage/Rules/Rules # defaultMessage: Available content rules: msgid "Available content rules:" -msgstr "" +msgstr "Regras de conteúdo disponíveis:" #: components/manage/Aliases/Aliases #: components/manage/Contents/Contents @@ -484,7 +484,7 @@ msgstr "Por padrão, as permissões do contêiner deste item são herdadas. Se v #: components/manage/Contents/Contents # defaultMessage: By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first. msgid "By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first." -msgstr "" +msgstr "Ao excluir esse item, você quebrará os links que existem nos itens listados abaixo. Se isso é realmente o que você deseja fazer, recomendamos que remova essas referências primeiro." #: components/manage/Controlpanels/DatabaseInformation # defaultMessage: Cache Name @@ -665,32 +665,32 @@ msgstr "Comparar" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition changed msgid "Condition changed" -msgstr "" +msgstr "Condição alterada" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition: msgid "Condition: " -msgstr "" +msgstr "Condição: " #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Configuration Versions msgid "Configuration Versions" -msgstr "" +msgstr "Versões da configuração" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Configure Content Rule msgid "Configure Content Rule" -msgstr "" +msgstr "Configurar regra de conteúdo" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Configure Content Rule: {title} msgid "Configure Content Rule: {title}" -msgstr "" +msgstr "Configurar regra de conteúdo: {title}" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Configure content rule msgid "Configure content rule" -msgstr "" +msgstr "Configurar regra de conteúdo" #: components/manage/Preferences/ChangePassword #: components/theme/PasswordReset/PasswordReset @@ -721,23 +721,23 @@ msgstr "Conteúdo" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Content Rule msgid "Content Rule" -msgstr "" +msgstr "Regra de conteúdo" #: components/manage/Controlpanels/Controlpanels #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Content Rules msgid "Content Rules" -msgstr "" +msgstr "Regras de conteúdo" #: components/manage/Rules/Rules # defaultMessage: Content rules for {title} msgid "Content rules for {title}" -msgstr "" +msgstr "Regras de conteúdo para {title}" #: components/manage/Rules/Rules # defaultMessage: Content rules from parent folders msgid "Content rules from parent folders" -msgstr "" +msgstr "Regras de conteúdo de pastas pai" #: components/manage/Controlpanels/ContentTypes # defaultMessage: Content type created @@ -816,7 +816,7 @@ msgstr "Critérios" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Current active configuration msgid "Current active configuration" -msgstr "" +msgstr "Configuração ativa atualmente" #: components/manage/Blocks/Search/components/FilterList # defaultMessage: Current filters applied @@ -905,7 +905,7 @@ msgstr "Padrão" #: config/Views # defaultMessage: Default view msgid "Default view" -msgstr "" +msgstr "Visão padrão" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsItem @@ -940,7 +940,7 @@ msgstr "Excluir Usuário" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action deleted msgid "Delete action" -msgstr "" +msgstr "Remover ação" #: helpers/MessageLabels/MessageLabels # defaultMessage: undefined @@ -955,7 +955,7 @@ msgstr "Excluir coluna" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition deleted msgid "Delete condition" -msgstr "" +msgstr "Remover condição" #: components/manage/Blocks/Table/Edit # defaultMessage: Delete row @@ -965,7 +965,7 @@ msgstr "Excluir linha" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Deleted msgid "Deleted" -msgstr "" +msgstr "Removida" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Depth @@ -1002,22 +1002,22 @@ msgstr "Diferenças entre as revisões {one} e {two} de {title}" #: components/manage/Rules/Rules # defaultMessage: Disable msgid "Disable" -msgstr "" +msgstr "Desativar" #: components/manage/Rules/Rules # defaultMessage: Disable apply to subfolders msgid "Disable apply to subfolders" -msgstr "" +msgstr "Desabilitar a aplicação a subpastas" #: components/manage/Rules/Rules # defaultMessage: Disabled msgid "Disabled" -msgstr "" +msgstr "Desativada" #: components/manage/Rules/Rules # defaultMessage: Disabled apply to subfolders msgid "Disabled apply to subfolders" -msgstr "" +msgstr "Desativada a aplicação em subpastas" #: components/theme/Footer/Footer # defaultMessage: Distributed under the {license}. @@ -1063,7 +1063,7 @@ msgstr "Documento" #: config/Views # defaultMessage: Document view msgid "Document view" -msgstr "" +msgstr "Visão do Documento" #: components/theme/EventDetails/EventDetails # defaultMessage: Download Event @@ -1073,7 +1073,7 @@ msgstr "Baixar evento" #: components/manage/Contents/ContentsUploadModal # defaultMessage: Drag and drop files from your computer onto this area or click the “Browse” button. msgid "Drag and drop files from your computer onto this area or click the “Browse” button." -msgstr "Arraste e solte arquivos do seu computador para esta área ou clique no botão "Procurar"." +msgstr "Arraste e solte arquivos do seu computador para esta área, ou clique no botão ‘Procurar’." #: components/manage/Widgets/FileWidget # defaultMessage: Drop file here to replace the existing file @@ -1093,7 +1093,7 @@ msgstr "Soltar aquivos aqui…" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Dry run selected, transaction aborted. msgid "Dry run selected, transaction aborted." -msgstr "" +msgstr "Simulação selecionada, transação abortada." #: components/theme/Register/Register # defaultMessage: E-mail @@ -1120,7 +1120,7 @@ msgstr "Editar" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Edit Rule msgid "Edit Rule" -msgstr "" +msgstr "Editar Regra" #: components/theme/Comments/CommentEditModal # defaultMessage: Edit comment @@ -1175,7 +1175,7 @@ msgstr "Lista de objetos vazia" #: components/manage/Rules/Rules # defaultMessage: Enable msgid "Enable" -msgstr "" +msgstr "Ativar" #: components/manage/Controlpanels/ContentTypeLayout # defaultMessage: Enable editable Blocks @@ -1185,17 +1185,17 @@ msgstr "Habilitar blocos editáveis" #: components/manage/Rules/Rules # defaultMessage: Enabled msgid "Enabled" -msgstr "" +msgstr "Ativada" #: components/manage/Rules/Rules # defaultMessage: Enabled here? msgid "Enabled here?" -msgstr "" +msgstr "Ativada aqui?" #: components/manage/Rules/Rules # defaultMessage: Enabled? msgid "Enabled?" -msgstr "" +msgstr "Ativada?" #: components/manage/Blocks/Search/components/DateRangeFacet #: components/manage/Contents/Contents @@ -1231,13 +1231,13 @@ msgstr "Informe o código Embed do mapa" #: components/manage/Controlpanels/Aliases # defaultMessage: Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target. msgid "Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target." -msgstr "" +msgstr "Informe o caminho absoluto do destino. O caminho deve começar com '/'. O destino deve existir ou ser um caminho de URL alternativo existente para o destino." #: components/manage/Aliases/Aliases #: components/manage/Controlpanels/Aliases # defaultMessage: Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring. msgid "Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring." -msgstr "" +msgstr "Informe o caminho absoluto onde a URL alternativa deve existir. O caminho deve começar com '/'. Somente urls que resultam em uma página 404 não encontrada resultarão em um redirecionamento ocorrendo." #: components/manage/Preferences/ChangePassword # defaultMessage: Enter your current password. @@ -1278,22 +1278,22 @@ msgstr "Erro" #: components/manage/Controlpanels/Aliases # defaultMessage: Error msgid "ErrorHeader" -msgstr "" +msgstr "Erro" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Event msgid "Event" -msgstr "" +msgstr "Evento" #: config/Views # defaultMessage: Event listing msgid "Event listing" -msgstr "" +msgstr "Listagem de Evento" #: config/Views # defaultMessage: Event view msgid "Event view" -msgstr "" +msgstr "Visão de Evento" #: components/manage/Contents/ContentsPropertiesModal # defaultMessage: Exclude from navigation @@ -1313,7 +1313,7 @@ msgstr "Excluído da navegação" #: components/manage/Aliases/Aliases # defaultMessage: Existing alternative urls for this item msgid "Existing alternative urls for this item" -msgstr "" +msgstr "URLs alternativas existentes para este item" #: components/manage/Sidebar/Sidebar # defaultMessage: Expand sidebar @@ -1373,7 +1373,7 @@ msgstr "Facetas no topo" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Failed to undo transactions msgid "Failed To Undo Transactions" -msgstr "" +msgstr "Falha ao desfazer transações" #: components/manage/Blocks/Search/schema # defaultMessage: Field @@ -1393,7 +1393,7 @@ msgstr "Tamanho do arquivo" #: config/Views # defaultMessage: File view msgid "File view" -msgstr "" +msgstr "Visão de Arquivo" #: components/manage/Contents/ContentsUploadModal # defaultMessage: Filename @@ -1403,12 +1403,12 @@ msgstr "Nome do arquivo" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Filter Rules: msgid "Filter Rules:" -msgstr "" +msgstr "Filtrar regras:" #: components/manage/Controlpanels/Aliases # defaultMessage: Filter by prefix msgid "Filter by prefix" -msgstr "" +msgstr "Filtrar por prefixo" #: helpers/MessageLabels/MessageLabels # defaultMessage: Filter users by groups @@ -1443,7 +1443,7 @@ msgstr "Pasta" #: config/Views # defaultMessage: Folder listing msgid "Folder listing" -msgstr "" +msgstr "Listagem" #: components/theme/Forbidden/Forbidden # defaultMessage: Forbidden @@ -1544,7 +1544,7 @@ msgstr "Título" #: components/manage/Blocks/Listing/schema # defaultMessage: Headline level msgid "Headline level" -msgstr "" +msgstr "Nível do título" #: components/manage/Blocks/Search/schema # defaultMessage: Hidden facets will still filter the results if proper parameters are passed in URLs @@ -1592,7 +1592,7 @@ msgstr "ID" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: If all of the following conditions are met: msgid "If all of the following conditions are met:" -msgstr "" +msgstr "Se todas as condições a seguir forem satisfeitas:" #: components/manage/Contents/ContentsPropertiesModal # defaultMessage: If selected, this item will not appear in the navigation tree @@ -1636,7 +1636,7 @@ msgstr "Tamanho da imagem" #: config/Views # defaultMessage: Image view msgid "Image view" -msgstr "" +msgstr "Visão de Imagem" #: components/manage/Widgets/RecurrenceWidget/Occurences # defaultMessage: Include this occurence @@ -1654,7 +1654,7 @@ msgstr "Informação" #: components/manage/Controlpanels/Users/UserGroupMembershipControlPanel # defaultMessage: You have selected the option 'many users' or 'many groups'. Thus this control panel asks for input to show users and groups. If you want to see users and groups instantaneous, head over to user group settings. See the button on the left. msgid "InfoUserGroupSettings" -msgstr "Você selecionou a opção "muitos usuários" ou "muitos grupos". Por isto, este painel necessita de um filtro para exibir usuários e grupos. Caso queira ver os usuários e grupos imediatamente, vá até configurações de usuários e grupos." +msgstr "Você selecionou a opção ‘muitos usuários’ ou ‘muitos grupos’. Por isto, este painel necessita de um filtro para exibir usuários e grupos. Caso queira ver os usuários e grupos imediatamente, vá até configurações de usuários e grupos." #: components/manage/Sharing/Sharing # defaultMessage: Inherit permissions from higher levels @@ -1729,7 +1729,7 @@ msgstr "Intervalo Anual" #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" -msgstr "" +msgstr "Bloco inválido" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Item batch size @@ -1781,7 +1781,7 @@ msgstr "Os itens devem ser únicos." #: components/manage/Contents/Contents # defaultMessage: Items to be deleted: msgid "Items to be deleted:" -msgstr "" +msgstr "Itens a serem removidos" #: components/manage/Blocks/Search/schema # defaultMessage: Label @@ -1821,7 +1821,7 @@ msgstr "Última modificação" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Latest available configuration msgid "Latest available configuration" -msgstr "" +msgstr "Configuração mais recente disponível" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Latest version @@ -1859,7 +1859,7 @@ msgstr "Mais" #: config/Views # defaultMessage: Link redirect view msgid "Link redirect view" -msgstr "" +msgstr "Visão de redireção de Link" #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema @@ -1888,7 +1888,7 @@ msgstr "Listagem" #: config/Views # defaultMessage: Listing view msgid "Listing view" -msgstr "" +msgstr "Listagem" #: components/theme/Comments/Comments # defaultMessage: Load more... @@ -1914,7 +1914,7 @@ msgstr "Entrar" #: components/theme/Logout/Logout # defaultMessage: Logged out msgid "Logged out" -msgstr "" +msgstr "Desconectado" #: components/theme/Login/Login # defaultMessage: Login @@ -1955,7 +1955,7 @@ msgstr "Gerenciar traduções" #: components/manage/Toolbar/More # defaultMessage: Manage content… msgid "Manage content…" -msgstr "" +msgstr "Gerenciar conteúdo..." #: components/manage/Multilingual/ManageTranslations # defaultMessage: Manage translations for {title} @@ -1965,12 +1965,12 @@ msgstr "Gerenciar traduções para {title}" #: components/manage/Controlpanels/Aliases # defaultMessage: Manual msgid "Manual" -msgstr "" +msgstr "Manual" #: components/manage/Controlpanels/Aliases # defaultMessage: Manually or automatically added? msgid "Manually or automatically added?" -msgstr "" +msgstr "Adicionado manual ou automaticamente?" #: components/manage/Blocks/Maps/MapsSidebar #: components/manage/Blocks/Maps/schema @@ -2047,17 +2047,17 @@ msgstr "Mais" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. msgid "More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide." -msgstr "" +msgstr "Mais informações sobre o procedimento de atualização podem ser encontradas na seção de documentação do plone.org no Guia de Atualização." #: config/Views # defaultMessage: Mosaic layout msgid "Mosaic layout" -msgstr "" +msgstr "Layout Mosaic" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Move down msgid "Move down" -msgstr "" +msgstr "Mover para baixo" #: components/manage/Contents/ContentsItem # defaultMessage: Move to bottom of folder @@ -2072,7 +2072,7 @@ msgstr "Mover para o topo da pasta" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Move up msgid "Move up" -msgstr "" +msgstr "Mover para cima" #: components/manage/Blocks/Search/schema # defaultMessage: Multiple choices? @@ -2098,7 +2098,7 @@ msgstr "Nome" #: components/manage/Widgets/AlignWidget # defaultMessage: Narrow msgid "Narrow" -msgstr "" +msgstr "Estreito" #: error # defaultMessage: Navigate back @@ -2124,7 +2124,7 @@ msgstr "Notícia" #: config/Views # defaultMessage: News item view msgid "News item view" -msgstr "" +msgstr "Visão de Notícia" #: components/manage/Contents/ContentsItem #: components/manage/Contents/ContentsPropertiesModal @@ -2136,17 +2136,17 @@ msgstr "Não" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: No transactions found msgid "No Transactions Found" -msgstr "" +msgstr "Nenhuma transação encontrada" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: No transactions selected msgid "No Transactions Selected" -msgstr "" +msgstr "Nenhuma transação selecionada" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: No transactions selected to do undo msgid "No Transactions Selected To Do Undo" -msgstr "" +msgstr "Nenhuma transação selecionada para ser desfeita" #: components/manage/Blocks/Video/VideoSidebar # defaultMessage: No Video selected @@ -2161,7 +2161,7 @@ msgstr "Nenhum complemento encontrado" #: components/theme/RequestTimeout/RequestTimeout # defaultMessage: There is no connection to the server, due to a timeout o no network connection. msgid "No connection to the server" -msgstr "" +msgstr "Sem conexão ao servidor" #: components/manage/Blocks/Image/ImageSidebar # defaultMessage: No image selected @@ -2257,7 +2257,7 @@ msgstr "Nenhum" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Note msgid "Note" -msgstr "" +msgstr "Nota" #: components/manage/Controlpanels/Users/UsersControlpanel # defaultMessage: Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group. @@ -2287,7 +2287,7 @@ msgstr "Ok" #: components/manage/Widgets/IdWidget # defaultMessage: Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed. msgid "Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed." -msgstr "" +msgstr "Apenas letras minúsculas (a-z) sem acentos, números (0-9), e os caracteres ‘-‘, ‘_’, e ‘.’ são permitidos." #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar @@ -2351,7 +2351,7 @@ msgstr "Colar blocos" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Perform the following actions: msgid "Perform the following actions:" -msgstr "" +msgstr "Realizar as seguintes ações:" #: components/manage/Sharing/Sharing # defaultMessage: Permissions have been updated successfully @@ -2388,12 +2388,12 @@ msgstr "Pessoas responsáveis pela criação do conteúdo deste item. Por favor, #: components/manage/Controlpanels/Controlpanels # defaultMessage: Please continue with the upgrade. msgid "Please continue with the upgrade." -msgstr "" +msgstr "Continue com a atualização." #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Please ensure you have a backup of your site before performing the upgrade. msgid "Please ensure you have a backup of your site before performing the upgrade." -msgstr "" +msgstr "Certifique-se de ter um backup do seu site antes de executar a atualização." #: components/manage/Blocks/Video/Body # defaultMessage: Please enter a valid URL by deleting the block and adding a new video block. @@ -2438,7 +2438,7 @@ msgstr "Plone{reg} Open Source CMS/WCM" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Position changed msgid "Position changed" -msgstr "" +msgstr "Posição alterada" #: components/manage/Widgets/SchemaWidget # defaultMessage: Possible values (Enter allowed choices one per line). @@ -2448,7 +2448,7 @@ msgstr "Valores possíveis" #: components/manage/Contents/Contents # defaultMessage: Potential link breakage msgid "Potential link breakage" -msgstr "" +msgstr "Potencial quebra de link" #: components/theme/Footer/Footer # defaultMessage: Powered by Plone & Python @@ -2564,7 +2564,7 @@ msgstr "Relevância" #: components/manage/Aliases/Aliases # defaultMessage: Remove msgid "Remove" -msgstr "" +msgstr "Remover" #: components/manage/Widgets/ObjectListWidget # defaultMessage: Remove item @@ -2579,7 +2579,7 @@ msgstr "Remover a recorrência" #: components/manage/Controlpanels/Aliases # defaultMessage: Remove selected msgid "Remove selected" -msgstr "" +msgstr "Remover selecionado" #: components/manage/Widgets/VocabularyTermsWidget # defaultMessage: Remove term @@ -2715,23 +2715,23 @@ msgstr "Raiz" #: components/manage/Controlpanels/Rules/AddRule # defaultMessage: Rule added msgid "Rule added" -msgstr "" +msgstr "Regra adicionada" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Rule enable changed msgid "Rule enable changed" -msgstr "" +msgstr "Regra alterada" #: components/manage/Rules/Rules #: components/manage/Toolbar/More # defaultMessage: Rules msgid "Rules" -msgstr "" +msgstr "Regras" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below. msgid "Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below." -msgstr "" +msgstr "As regras são realizadas quando ocorre um evento de disparo. As ações da regra só serão invocadas se todas as condições da regra forem atendidas. Você pode adicionar novas ações e condições usando os botões abaixo." #: components/manage/Add/Add #: components/manage/Controlpanels/ContentType @@ -2756,7 +2756,7 @@ msgstr "Salvar a recorrência" #: helpers/MessageLabels/MessageLabels # defaultMessage: Saved msgid "Saved" -msgstr "" +msgstr "Salvo" #: components/manage/Controlpanels/ContentTypesActions # defaultMessage: Schema @@ -2839,7 +2839,7 @@ msgstr "Pesquisar usuários…" #: components/manage/Blocks/Search/components/SearchDetails # defaultMessage: Searched for: {searchedtext}. msgid "Searched for: {searchedtext}." -msgstr "" +msgstr "Pesquisado por: {searchedtext}." #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Second @@ -2911,7 +2911,7 @@ msgstr "Enviar" #: helpers/MessageLabels/MessageLabels # defaultMessage: Send a confirmation mail with a link to set the password. msgid "Send a confirmation mail with a link to set the password." -msgstr "" +msgstr "Envie um e-mail de confirmação com um link para definir a senha." #: components/theme/PasswordReset/PasswordReset # defaultMessage: Set my password @@ -2995,7 +2995,7 @@ msgstr "Mostrar ordenação?" #: components/manage/Blocks/Search/schema # defaultMessage: Show total results msgid "Show total results" -msgstr "" +msgstr "Exibir todos os resultados" #: components/manage/Sidebar/Sidebar # defaultMessage: Shrink sidebar @@ -3052,7 +3052,7 @@ msgstr "Desculpe, algo deu errado com sua requisição" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Sort by msgid "Sort By" -msgstr "" +msgstr "Ordenar por" #: components/theme/Search/Search # defaultMessage: Sort by: @@ -3074,12 +3074,12 @@ msgstr "Opções de ordenação" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Sort transactions by User-Name, Path or Date msgid "Sort transactions by User-Name, Path or Date" -msgstr "" +msgstr "Ordenar transações por Nome de Usuário, Caminho ou Data" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Sorted msgid "Sorted" -msgstr "" +msgstr "Ordenado" #: components/manage/Blocks/HTML/Edit #: components/manage/Blocks/Image/schema @@ -3123,7 +3123,7 @@ msgstr "Estado" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Status msgid "Status" -msgstr "" +msgstr "Situação" #: components/manage/Multilingual/CompareLanguages # defaultMessage: Stop compare @@ -3175,7 +3175,7 @@ msgstr "Sucesso" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Successfully undone transactions msgid "Successfully Undone Transactions" -msgstr "" +msgstr "Transações desfeitas com sucesso" #: config/Blocks # defaultMessage: Summary @@ -3185,7 +3185,7 @@ msgstr "Resumo" #: config/Views # defaultMessage: Summary view msgid "Summary view" -msgstr "" +msgstr "Visão de resumo" #: components/theme/LanguageSelector/LanguageSelector # defaultMessage: Switch to @@ -3206,7 +3206,7 @@ msgstr "Tabela de conteúdos" #: config/Views # defaultMessage: Tabular view msgid "Tabular view" -msgstr "" +msgstr "Visão tabular" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsTagsModal @@ -3227,12 +3227,12 @@ msgstr "Tags a serem removidas" #: components/manage/Controlpanels/Aliases # defaultMessage: Target msgid "Target" -msgstr "" +msgstr "Destino" #: components/manage/Controlpanels/Aliases # defaultMessage: Target Path (Required) msgid "Target Path (Required)" -msgstr "" +msgstr "Caminho de destino (obrigatório)" #: components/manage/Controlpanels/DatabaseInformation # defaultMessage: Target memory size per cache in bytes @@ -3247,7 +3247,7 @@ msgstr "Número de objetos na memória para o cache" #: components/manage/Controlpanels/Aliases # defaultMessage: Target url path must start with a slash. msgid "Target url path must start with a slash." -msgstr "" +msgstr "O caminho da URL de destino deve começar com uma barra." #: components/manage/Widgets/SchemaWidget # defaultMessage: Text @@ -3271,7 +3271,7 @@ msgstr "O Gerenciador de banco de dados permite que você visualize informaçõe #: components/theme/RequestTimeout/RequestTimeout # defaultMessage: The backend is not responding, due to a server timeout or a connection problem of your device. Please check your connection and try again. msgid "The backend is not responding, due to a server timeout or a connection problem of your device. Please check your connection and try again." -msgstr "" +msgstr "O backend não está respondendo, devido a um tempo limite do servidor ou a um problema de conexão do seu dispositivo. Verifique sua conexão e tente novamente." #: components/theme/ConnectionRefused/ConnectionRefused # defaultMessage: The backend is not responding, please check if you have started Plone, check your project's configuration object apiPath (or if you are using the internal proxy, devProxyToApiPath) or the RAZZLE_API_PATH Volto's environment variable. @@ -3296,12 +3296,12 @@ msgstr "A presença do botão desativa a busca ativa, a consulta é realizada ap #: components/manage/Rules/Rules # defaultMessage: The following content rules are active in this Page. Use the content rules control panel to create new rules or delete or modify existing ones. msgid "The following content rules are active in this Page. Use the content rules control panel to create new rules or delete or modify existing ones." -msgstr "" +msgstr "As seguintes regras de conteúdo estão ativas nesta Página. Use o painel de controle de regras de conteúdo para criar novas regras ou excluir ou modificar as existentes." #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: The following list shows which upgrade steps are going to be run. Upgrading sometimes performs a catalog/security update, which may take a long time on large sites. Be patient. msgid "The following list shows which upgrade steps are going to be run. Upgrading sometimes performs a catalog/security update, which may take a long time on large sites. Be patient." -msgstr "" +msgstr "A lista a seguir mostra quais etapas de atualização serão realizados. Às vezes, a atualização executa um catálogo/atualização de segurança, o que pode levar muito tempo em sites grandes. Seja paciente." #: components/manage/Contents/Contents # defaultMessage: The item could not be deleted. @@ -3316,7 +3316,7 @@ msgstr "O endereço do link é:" #: components/manage/Aliases/Aliases # defaultMessage: The provided alternative url already exists! msgid "The provided alternative url already exists!" -msgstr "" +msgstr "A url alternativa já existe!" #: components/theme/Register/Register # defaultMessage: The registration process has been successful. Please check your e-mail inbox for information on how activate your account. @@ -3327,7 +3327,7 @@ msgstr "O processo de registro foi bem sucedido. Verifique sua caixa de entrada #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: The site configuration is outdated and needs to be upgraded. msgid "The site configuration is outdated and needs to be upgraded." -msgstr "" +msgstr "A configuração do site está desatualizada e precisa ser atualizada." #: components/manage/Toolbar/More # defaultMessage: The working copy was discarded @@ -3347,7 +3347,7 @@ msgstr "Há um problema de configuração no backend" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: There was an error with the upgrade. msgid "There was an error with the upgrade." -msgstr "" +msgstr "Ocorreu um erro com a atualização." #: components/manage/Form/InlineForm # defaultMessage: There were some errors @@ -3368,7 +3368,7 @@ msgstr "Terceiro" #: components/manage/Contents/Contents # defaultMessage: This Page is referenced by the following items: msgid "This Page is referenced by the following items:" -msgstr "" +msgstr "Esta página é referenciada pelos seguintes itens:" #: components/manage/WorkingCopyToastsFactory/WorkingCopyToastsFactory # defaultMessage: This has an ongoing working copy in {title} @@ -3403,7 +3403,7 @@ msgstr "Esta página parece não existir…" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: This rule is assigned to the following locations: msgid "This rule is assigned to the following locations:" -msgstr "" +msgstr "Essa regra é atribuída aos seguintes locais:" #: components/manage/Widgets/DatetimeWidget # defaultMessage: Time @@ -3427,7 +3427,7 @@ msgstr "Título" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Title field error. Value not provided or already existing. msgid "Title field error. Value not provided or already existing." -msgstr "" +msgstr "Erro de título. Valor não fornecido ou já existente." #: components/manage/Controlpanels/DatabaseInformation # defaultMessage: Total active and non-active objects @@ -3457,22 +3457,22 @@ msgstr "Número total de objetos no banco de dados" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Transactions msgid "Transactions" -msgstr "" +msgstr "Transações" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: # msgid "Transactions Checkbox" -msgstr "" +msgstr "Caixa de seleção Transações" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Transactions have been sorted msgid "Transactions Have Been Sorted" -msgstr "" +msgstr "As transações foram ordenadas" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Transactions have been unsorted msgid "Transactions Have Been Unsorted" -msgstr "" +msgstr "As transações não foram ordenadas" #: components/manage/Add/Add #: components/manage/Toolbar/Types @@ -3494,7 +3494,7 @@ msgstr "Vínculo de tradução removido" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Triggering event field error. Please select a value msgid "Triggering event field error. Please select a value" -msgstr "" +msgstr "Erro no campo de gatilho. Selecione um valor" #: components/manage/Controlpanels/ContentTypes #: components/manage/Widgets/SchemaWidget @@ -3521,7 +3521,7 @@ msgstr "Digite texto…" #: components/manage/TextLineEdit/TextLineEdit # defaultMessage: Type the heading… msgid "Type the heading…" -msgstr "" +msgstr "Digite o cabeçalho..." #: components/manage/Blocks/Title/Edit # defaultMessage: Type the title… @@ -3539,22 +3539,22 @@ msgstr "UID" #: components/manage/Toolbar/More # defaultMessage: URL Management msgid "URL Management" -msgstr "" +msgstr "Gerenciamento de URL" #: components/manage/Aliases/Aliases # defaultMessage: URL Management for {title} msgid "URL Management for {title}" -msgstr "" +msgstr "Gerenciamento de URL para {title}" #: components/manage/Rules/Rules # defaultMessage: Unassign msgid "Unassign" -msgstr "" +msgstr "Cancelar atribuição" #: components/manage/Rules/Rules # defaultMessage: Unassigned msgid "Unassigned" -msgstr "" +msgstr "Não atribuído" #: components/manage/Toolbar/More #: components/theme/Unauthorized/Unauthorized @@ -3572,7 +3572,7 @@ msgstr "Desfazer" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Undo Controlpanel msgid "Undo Controlpanel" -msgstr "" +msgstr "Painel de Controle de Undo" #: components/manage/BlockChooser/BlockChooser # defaultMessage: Unfold @@ -3608,7 +3608,7 @@ msgstr "Destravar" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Unsorted msgid "Unsorted" -msgstr "" +msgstr "Desordenado" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Update @@ -3633,22 +3633,22 @@ msgstr "Atualizações disponíveis" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade msgid "Upgrade" -msgstr "" +msgstr "Atualizar" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Plone Site msgid "Upgrade Plone Site" -msgstr "" +msgstr "Atualizar site Plone" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Report msgid "Upgrade Report" -msgstr "" +msgstr "Relatório de atualização" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Steps msgid "Upgrade Steps" -msgstr "" +msgstr "Passos de atualização" #: components/manage/Contents/Contents # defaultMessage: Upload @@ -3684,12 +3684,12 @@ msgstr "Enviando imagem" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Use the form below to define the new content rule msgid "Use the form below to define the new content rule" -msgstr "" +msgstr "Use o formulário abaixo para definir a nova regra de conteúdo" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Use the form below to define, change or remove content rules. Rules will automatically perform actions on content when certain triggers take place. After defining rules, you may want to go to a folder to assign them, using the 'rules' item in the actions menu. msgid "Use the form below to define, change or remove content rules. Rules will automatically perform actions on content when certain triggers take place. After defining rules, you may want to go to a folder to assign them, using the 'rules' item in the actions menu." -msgstr "" +msgstr "Use o formulário abaixo para definir, alterar ou remover regras de conteúdo. As regras executarão automaticamente ações no conteúdo quando determinados gatilhos ocorrerem. Depois de definir regras, você pode querer ir para uma pasta para atribuí-las, usando o item 'regras' no menu de ações." #: components/manage/Widgets/SchemaWidget #: components/manage/Widgets/SelectWidget @@ -3749,7 +3749,7 @@ msgstr "Usuários e Grupos" #: components/manage/Aliases/Aliases # defaultMessage: Using this form, you can manage alternative urls for an item. This is an easy way to make an item available under two different URLs. msgid "Using this form, you can manage alternative urls for an item. This is an easy way to make an item available under two different URLs." -msgstr "" +msgstr "Usando esse formulário, você pode gerenciar URLs alternativas para um item. Essa é uma maneira fácil de disponibilizar um item em duas URLs diferentes." #: helpers/Extensions/withBlockSchemaEnhancer # defaultMessage: Variation @@ -3869,19 +3869,19 @@ msgstr "Quando chegar a esta data, o conteúdo deixará de ficar visível nas li #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Whether or not execution of further rules should stop after this rule is executed msgid "Whether or not execution of further rules should stop after this rule is executed" -msgstr "" +msgstr "Se a execução de outras regras deve ou não parar depois que essa regra for executada" #: components/manage/Controlpanels/Rules/AddRule #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops msgid "Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops" -msgstr "" +msgstr "Se outras regras devem ou não ser acionadas pelas ações lançadas por esta regra. Ative isso somente se tiver certeza de que isso não criará loops infinitos" #: components/manage/Controlpanels/Rules/AddRule #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Whether or not the rule is currently enabled msgid "Whether or not the rule is currently enabled" -msgstr "" +msgstr "Se a regra está ou não habilitada no momento" #: components/manage/Controlpanels/UndoControlpanel #: components/manage/History/History @@ -3955,7 +3955,7 @@ msgstr "Você não pode colar este conteúdo aqui" #: components/theme/Logout/Logout # defaultMessage: You have been logged out from the site. msgid "You have been logged out from the site." -msgstr "" +msgstr "Você foi desconectado do site." #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Your email is required for reset your password. @@ -3975,7 +3975,7 @@ msgstr "O seu idioma preferido" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Your site is up to date. msgid "Your site is up to date." -msgstr "" +msgstr "Seu site está atualizado." #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Your username is required for reset your password. @@ -3985,7 +3985,7 @@ msgstr "Seu nome de usuário é necessário para redefinir sua senha." #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" -msgstr "" +msgstr "Visões disponíveis" #: components/theme/Login/Login #: components/theme/PasswordReset/RequestPasswordReset @@ -3996,7 +3996,7 @@ msgstr "Esqueceu sua senha?" #: config/Blocks # defaultMessage: Checkbox msgid "checkboxFacet" -msgstr "" +msgstr "Caixa de seleção" #: config/Blocks # defaultMessage: Common @@ -4011,7 +4011,7 @@ msgstr "Comparar com" #: config/Blocks # defaultMessage: Date Range msgid "daterangeFacet" -msgstr "" +msgstr "Intervalo de datas" #: components/manage/Blocks/Block/EditBlockWrapper # defaultMessage: delete @@ -4220,7 +4220,7 @@ msgstr "outros" #: components/manage/Contents/ContentsItem # defaultMessage: Pending msgid "pending" -msgstr "" +msgstr "pendente" #: components/manage/Contents/ContentsItem # defaultMessage: Private @@ -4410,12 +4410,12 @@ msgstr "anos" #: config/Blocks # defaultMessage: Select msgid "selectFacet" -msgstr "" +msgstr "Seleção" #: components/manage/Blocks/Search/components/ViewSwitcher # defaultMessage: Select view msgid "selectView" -msgstr "" +msgstr "Escolha a visão" #: components/theme/SkipLinks/SkipLinks # defaultMessage: Skip to footer @@ -4460,12 +4460,12 @@ msgstr "tabela de conteúdos" #: config/Blocks # defaultMessage: Toggle msgid "toggleFacet" -msgstr "" +msgstr "Alternar" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Update from version {origin} to {destination} msgid "upgradeVersions" -msgstr "" +msgstr "Atualizar da versão {origin} para versão {destination}" #: helpers/MessageLabels/MessageLabels # defaultMessage: Input must be valid url (www.something.com or http(s)://www.something.com) @@ -4485,7 +4485,7 @@ msgstr "vídeo" #: components/manage/Blocks/Search/schema # defaultMessage: Views msgid "views" -msgstr "" +msgstr "Visões" #: components/theme/EventDetails/EventDetails # defaultMessage: Visit external website @@ -4495,12 +4495,12 @@ msgstr "Visitar site externo" #: components/manage/Toolbar/More # defaultMessage: You are not authorized to perform this operation. msgid "workingCopyErrorUnauthorized" -msgstr "" +msgstr "Você não possui autorização para realizar esta operação" #: components/manage/Toolbar/More # defaultMessage: An error occurred while performing this operation. msgid "workingCopyGenericError" -msgstr "" +msgstr "Ocorreu um erro ao realizar esta operação" #: components/manage/Blocks/Search/components/DateRangeFacetFilterListEntry #: components/manage/Blocks/Search/components/ToggleFacetFilterListEntry From 1063937b2d76aa3e4aaebf93965622ff7ab5d3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Tue, 15 Nov 2022 15:36:17 +0100 Subject: [PATCH 009/326] Revert #2828 PR change of the default `showSearchButton` Search block behavior (#3884) --- CHANGELOG.md | 1 + src/components/manage/Blocks/Search/schema.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b62e3560d..984a2ce622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Load core add-ons configuration as any other add-on. @sneridagh - Fix `FormValidation` error object, use field `id` instead of field `title` @sneridagh +- Revert #2828 PR change of the default `showSearchButton` Search block behavior (see [#3883](https://github.com/plone/volto/issues/3883)) @sneridagh ### Internal diff --git a/src/components/manage/Blocks/Search/schema.js b/src/components/manage/Blocks/Search/schema.js index f8f198048d..c90aa57d98 100644 --- a/src/components/manage/Blocks/Search/schema.js +++ b/src/components/manage/Blocks/Search/schema.js @@ -240,7 +240,6 @@ const SearchSchema = ({ data = {}, intl }) => { type: 'boolean', title: intl.formatMessage(messages.showSearchButtonTitle), description: intl.formatMessage(messages.showSearchButtonDescription), - default: true, }, showTotalResults: { type: 'boolean', From 71165bac3c2e5048b2dea5d4cf11c630a17baa5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Tue, 15 Nov 2022 15:36:38 +0100 Subject: [PATCH 010/326] Fix condition in `applySchemaDefaults` (#3887) --- CHANGELOG.md | 1 + src/helpers/Blocks/Blocks.js | 2 +- src/helpers/Blocks/Blocks.test.js | 8 +++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 984a2ce622..2350215b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Bugfix +- Fix condition in `applySchemaDefaults` @tiberiuichim @sneridagh - Load core add-ons configuration as any other add-on. @sneridagh - Fix `FormValidation` error object, use field `id` instead of field `title` @sneridagh - Revert #2828 PR change of the default `showSearchButton` Search block behavior (see [#3883](https://github.com/plone/volto/issues/3883)) @sneridagh diff --git a/src/helpers/Blocks/Blocks.js b/src/helpers/Blocks/Blocks.js index 922fbb38c7..3e9c3aa881 100644 --- a/src/helpers/Blocks/Blocks.js +++ b/src/helpers/Blocks/Blocks.js @@ -372,7 +372,7 @@ export function visitBlocks(content, callback) { export function applySchemaDefaults({ data = {}, schema }) { const derivedData = { ...Object.keys(schema.properties).reduce((accumulator, currentField) => { - return schema.properties[currentField].default + return typeof schema.properties[currentField].default !== 'undefined' ? { ...accumulator, [currentField]: schema.properties[currentField].default, diff --git a/src/helpers/Blocks/Blocks.test.js b/src/helpers/Blocks/Blocks.test.js index 122f841801..31acee4cfb 100644 --- a/src/helpers/Blocks/Blocks.test.js +++ b/src/helpers/Blocks/Blocks.test.js @@ -38,7 +38,7 @@ config.blocks.blocksConfig.text = { fieldsets: [ { id: 'default', - fields: ['title', 'description', 'nonDefault'], + fields: ['title', 'description', 'nonDefault', 'booleanField'], title: 'Default', }, ], @@ -52,6 +52,10 @@ config.blocks.blocksConfig.text = { nonDefault: { title: 'Non default', }, + booleanField: { + title: 'BooleanField', + default: false, + }, }, }), }; @@ -499,6 +503,7 @@ describe('Blocks', () => { const schema = config.blocks.blocksConfig.text.blockSchema({ data }); expect(applySchemaDefaults({ schema, data })).toEqual({ '@type': 'text', + booleanField: false, title: 'Default title', description: 'already filled', }); @@ -513,6 +518,7 @@ describe('Blocks', () => { }; expect(applyBlockDefaults({ data })).toEqual({ '@type': 'text', + booleanField: false, title: 'Default title', description: 'already filled', }); From 03464c30dd342418a16bf1825fddff0dc9ff14f7 Mon Sep 17 00:00:00 2001 From: Mauro Amico Date: Tue, 15 Nov 2022 15:37:01 +0100 Subject: [PATCH 011/326] typo in upgrade guide for .yarnrc.yml? (#3885) --- docs/source/upgrade-guide/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 341093ff34..2987e66fc9 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -224,7 +224,7 @@ Volto was using the old, classic yarn (v1). It has become quite obsolete and yarn has evolved a lot during the last years. We are updating Volto to be able to use it, however some changes have to be made in your projects configuration: -1. Enable yarn 3 in your projects, adding `yarnrc.yml`: +1. Enable yarn 3 in your projects, adding `.yarnrc.yml`: ```yml defaultSemverRangePrefix: "" From 7defdc1b8febb0c6095339e5ab8034ff672286bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dante=20=C3=81lvarez?= <89805481+danalvrz@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:58:49 -0600 Subject: [PATCH 012/326] Storybook component update (#3875) * PreviewImage story draft * Edit PreviewImage * ColorPicker draft * ColorPicker story * FormFieldWrapper story draft * InternalUrlWidget story draft * Login component story draft * Move static folder for storybook * Minor changes to stories * Minor change in component story * Delete story Co-authored-by: Tiberiu Ichim --- .storybook/main.js | 1 + .storybook/static/previewImage.png | Bin 0 -> 8740 bytes CHANGELOG.md | 2 + .../Widgets/ColorPickerWidget.stories.jsx | 30 +++++ .../Widgets/FormFieldWrapper.stories.jsx | 107 ++++++++++++++++++ .../Widgets/InternalUrlWidget.stories.jsx | 65 +++++++++++ src/components/theme/Login/Login.stories.jsx | 43 +++++++ 7 files changed, 248 insertions(+) create mode 100644 .storybook/static/previewImage.png create mode 100644 src/components/manage/Widgets/ColorPickerWidget.stories.jsx create mode 100644 src/components/manage/Widgets/FormFieldWrapper.stories.jsx create mode 100644 src/components/manage/Widgets/InternalUrlWidget.stories.jsx create mode 100644 src/components/theme/Login/Login.stories.jsx diff --git a/.storybook/main.js b/.storybook/main.js index cc042c2800..877714c064 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -58,6 +58,7 @@ const defaultRazzleOptions = { module.exports = { stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], addons: ['@storybook/addon-links', '@storybook/addon-essentials'], + staticDirs: ['./static'], webpackFinal: async (config, { configType }) => { // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' // You can change the configuration based on that. diff --git a/.storybook/static/previewImage.png b/.storybook/static/previewImage.png new file mode 100644 index 0000000000000000000000000000000000000000..4283b3fa3cc07060b207e26a165c50a5833429df GIT binary patch literal 8740 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%0Ath-IDL; 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 WF;a6F%wK ( +
+ +
+ ), + ], + argTypes: {}, +}; diff --git a/src/components/manage/Widgets/FormFieldWrapper.stories.jsx b/src/components/manage/Widgets/FormFieldWrapper.stories.jsx new file mode 100644 index 0000000000..750420fb88 --- /dev/null +++ b/src/components/manage/Widgets/FormFieldWrapper.stories.jsx @@ -0,0 +1,107 @@ +import React from 'react'; +import FormFieldWrapper from './FormFieldWrapper'; +import WidgetStory from './story'; + +export const Default = WidgetStory.bind({ + widget: FormFieldWrapper, + props: { children: React.createElement('input', { className: 'blue' }) }, +}); +Default.args = { + id: 'fieldWrapper', + title: 'Field Wrapper', + description: 'Optional text', +}; + +export const Errored = WidgetStory.bind({ + widget: FormFieldWrapper, + props: { children: React.createElement('input', { className: 'blue' }) }, +}); +Errored.args = { + id: 'fieldWrapper', + title: 'Field Wrapper', + description: 'Optional help text', + error: ['this is an error'], +}; + +export const SingleColumn = WidgetStory.bind({ + widget: FormFieldWrapper, + props: { children: React.createElement('input', { className: 'blue' }) }, +}); +SingleColumn.args = { + id: 'fieldWrapper', + title: 'Field Wrapper', + columns: 1, + description: 'Optional help text', +}; + +export const Required = WidgetStory.bind({ + widget: FormFieldWrapper, + props: { children: React.createElement('input', { className: 'blue' }) }, +}); +Required.args = { + id: 'fieldWrapper', + title: 'Field Wrapper', + required: true, + description: 'Optional text', +}; + +export const Unwrapped = WidgetStory.bind({ + widget: FormFieldWrapper, + props: { children: React.createElement('input', { className: 'blue' }) }, +}); +Unwrapped.args = { + id: 'fieldWrapper', + title: 'Field Wrapper', + wrapped: false, + description: 'Optional text', +}; +// ** Currently included in OnEdit ** +// export const Draggable = WidgetStory.bind({ +// widget: FormFieldWrapper, +// props: { children: React.createElement('input', { className: 'blue' }) }, +// }); +// Draggable.args = { +// id: 'fieldWrapper', +// title: 'Field Wrapper', +// draggable: true, +// onEdit: true, +// description: 'Optional text', +// }; + +// export const Disabled = WidgetStory.bind({ +// widget: FormFieldWrapper, +// props: { children: React.createElement('input', { className: 'blue' }) }, +// }); +// Disabled.args = { +// id: 'fieldWrapper', +// title: 'Field Wrapper', +// isDisabled: true, +// onEdit: true, +// description: 'Optional text', +// }; + +export const OnEdit = WidgetStory.bind({ + widget: FormFieldWrapper, + props: { children: React.createElement('input', { className: 'blue' }) }, +}); +OnEdit.args = { + id: 'fieldWrapper', + title: 'Field Wrapper', + onEdit: true, + draggable: false, + isDisabled: false, + description: 'Optional text', +}; + +export default { + title: 'Edit Widgets/FormFieldWrapper', + component: Default, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: {}, +}; diff --git a/src/components/manage/Widgets/InternalUrlWidget.stories.jsx b/src/components/manage/Widgets/InternalUrlWidget.stories.jsx new file mode 100644 index 0000000000..3c39e5e648 --- /dev/null +++ b/src/components/manage/Widgets/InternalUrlWidget.stories.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import InternalUrlWidget from './InternalUrlWidget'; +import WidgetStory from './story'; + +export const Default = WidgetStory.bind({ + widget: InternalUrlWidget, +}); +Default.args = { + id: 'internalUrl', + title: 'Internal Url', + description: 'Optional help text', +}; + +export const Placeholder = WidgetStory.bind({ + widget: InternalUrlWidget, +}); +Placeholder.args = { + id: 'internalUrl', + title: 'Internal Url', + description: 'Optional help text', + placeholder: 'Placeholder text', +}; + +export const Required = WidgetStory.bind({ + widget: InternalUrlWidget, +}); +Required.args = { + id: 'internalUrl', + title: 'Internal Url', + description: 'Optional help text', + required: true, +}; + +export const Errored = WidgetStory.bind({ + widget: InternalUrlWidget, +}); +Errored.args = { + id: 'internalUrl', + title: 'Internal Url', + description: 'Optional help text', + error: ['this is an error'], +}; +export const CustomLength = WidgetStory.bind({ + widget: InternalUrlWidget, +}); +CustomLength.args = { + id: 'internalUrl', + title: 'Internal Url', + description: 'Optional help text', + minLength: 3, + maxLength: 5, +}; + +export default { + title: 'Edit Widgets/InternalUrlWidget', + component: Default, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: {}, +}; diff --git a/src/components/theme/Login/Login.stories.jsx b/src/components/theme/Login/Login.stories.jsx new file mode 100644 index 0000000000..b15d509705 --- /dev/null +++ b/src/components/theme/Login/Login.stories.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import Login from './Login'; +import Wrapper from '@plone/volto/storybook'; + +const { Provider } = require('react-intl-redux'); +const configureStore = require('redux-mock-store').default; +const store = configureStore()({ + userSession: { + login: {}, + }, + intl: { + locale: 'en', + messages: {}, + }, +}); + +const StoryComponent = (args) => { + return ( + + + + + + ); +}; + +export const Default = StoryComponent.bind({}); +Default.args = { + login: () => null, +}; + +export default { + title: 'Public Components/Login', + component: Default, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + argTypes: {}, +}; From 109b6676ffc736f44548664f76786f4a16cfb1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Tue, 15 Nov 2022 20:14:08 +0100 Subject: [PATCH 013/326] Missing `.yarnrc.yml` entry for the yarn 3 release (#3893) * Missing `.yarnrc.yml` entry for the yarn 3 release * Fix `package.json` `postinstall` in core --- CHANGELOG.md | 1 + package.json | 2 +- packages/generator-volto/CHANGELOG.md | 6 +----- .../generator-volto/generators/app/templates/.yarnrc.yml | 2 ++ 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1220fe722..35ff9fe1e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Load core add-ons configuration as any other add-on. @sneridagh - Fix `FormValidation` error object, use field `id` instead of field `title` @sneridagh - Revert #2828 PR change of the default `showSearchButton` Search block behavior (see [#3883](https://github.com/plone/volto/issues/3883)) @sneridagh +- Fix `package.json` `postinstall` in core @sneridagh ### Internal diff --git a/package.json b/package.json index 7ffb632ff4..7c0931c2b5 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ } }, "scripts": { - "postinstall": "yarn patches", + "postinstall": "make patches", "analyze": "BUNDLE_ANALYZE=true razzle build", "start": "razzle start", "build": "razzle build --noninteractive", diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index cb53e9e1f5..3f5861e62f 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -2,13 +2,9 @@ ## 6.0.0 (unreleased) -### Breaking - -### Feature - ### Bugfix -### Internal +- Missing `.yarnrc.yml` entry for the yarn 3 release @sneridagh ## 6.0.0-alpha.1 (2022-11-14) diff --git a/packages/generator-volto/generators/app/templates/.yarnrc.yml b/packages/generator-volto/generators/app/templates/.yarnrc.yml index 7ba07ff289..7af626ed79 100644 --- a/packages/generator-volto/generators/app/templates/.yarnrc.yml +++ b/packages/generator-volto/generators/app/templates/.yarnrc.yml @@ -1,3 +1,5 @@ defaultSemverRangePrefix: "" nodeLinker: node-modules + +yarnPath: .yarn/releases/yarn-3.2.3.cjs From 92c0dfbe82bc11d8ba774b7bb1c241655d3d61dc Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 15 Nov 2022 20:17:26 +0100 Subject: [PATCH 014/326] Prepare for release --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35ff9fe1e2..adb148f43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ## 16.0.0 (unreleased) -### Breaking - ### Feature - Brazilian Portuguese translation updated @ericof @@ -16,13 +14,10 @@ - Revert #2828 PR change of the default `showSearchButton` Search block behavior (see [#3883](https://github.com/plone/volto/issues/3883)) @sneridagh - Fix `package.json` `postinstall` in core @sneridagh -### Internal - ### Documentation - Add missing pieces of the upgrade to use yarn 3 for projects @sneridagh - Complete docs about the yarn 3 upgrade @sneridagh - - Add additional components to storybook @danalvrz ## 16.0.0-alpha.49 (2022-11-11) From 70d643a61a4e1ccae1b56caed1ac21e8d5a1199a Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 15 Nov 2022 20:18:09 +0100 Subject: [PATCH 015/326] Release 16.0.0-alpha.50 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adb148f43b..135d15ca7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 16.0.0 (unreleased) +## 16.0.0-alpha.50 (2022-11-15) ### Feature diff --git a/package.json b/package.json index 7c0931c2b5..0ce55baf09 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "16.0.0-alpha.49", + "version": "16.0.0-alpha.50", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 33d2c6d416b2368c64da66d29dd92ab737535f2b Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 15 Nov 2022 20:18:15 +0100 Subject: [PATCH 016/326] Back to development --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 135d15ca7a..928196fed6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 16.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + +### Documentation + ## 16.0.0-alpha.50 (2022-11-15) ### Feature From a534f9ad610f90eb4b200d7a9ac9edc72e5a5792 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 15 Nov 2022 20:19:05 +0100 Subject: [PATCH 017/326] Release generate-volto 6.0.0-alpha.2 --- packages/generator-volto/CHANGELOG.md | 2 +- packages/generator-volto/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 3f5861e62f..7feedc1f24 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 6.0.0 (unreleased) +## 6.0.0-alpha.2 (2022-11-15) ### Bugfix diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index 7ab993da85..db412ec424 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "6.0.0-alpha.1", + "version": "6.0.0-alpha.2", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" From f416089ddfcd9ac743e5dd043e5bcbc5cbf011ce Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 15 Nov 2022 20:19:11 +0100 Subject: [PATCH 018/326] Back to development (generator-volto) --- packages/generator-volto/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 7feedc1f24..ffbc71ab6c 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## 6.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + ## 6.0.0-alpha.2 (2022-11-15) ### Bugfix From 721dd0731ddb44391be395551d70f03d9c2d40ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 16 Nov 2022 10:24:09 +0100 Subject: [PATCH 019/326] Add @plone/scripts to app generator (#3899) * Add @plone/scripts to app generator * Typo --- packages/generator-volto/CHANGELOG.md | 2 ++ .../generators/app/templates/package.json.tpl | 1 + packages/generator-volto/package.json | 6 +----- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index ffbc71ab6c..3937716d67 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugfix +- Add missing `"@plone/scripts": "^2.1.2"` devDependency to app template @sneridagh + ### Internal ## 6.0.0-alpha.2 (2022-11-15) diff --git a/packages/generator-volto/generators/app/templates/package.json.tpl b/packages/generator-volto/generators/app/templates/package.json.tpl index 44d8084609..ad1e31a9c6 100644 --- a/packages/generator-volto/generators/app/templates/package.json.tpl +++ b/packages/generator-volto/generators/app/templates/package.json.tpl @@ -143,6 +143,7 @@ "mrs-developer": "*", "postcss": "8.4.13", "prettier": "2.0.5", + "@plone/scripts": "^2.1.2", "@storybook/addon-actions": "^6.3.0", "@storybook/addon-controls": "6.3.0", "@storybook/addon-essentials": "^6.3.0", diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index db412ec424..0ab2e73031 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -50,7 +50,6 @@ "ci:cypress:run": "start-test ci:start-api-plone http-get://localhost:55001/plone ci:start-frontend", "ci:cypress:run:guillotina": "start-test ci:start-api-guillotina http-get://localhost:8081 ci:start-frontend-guillotina", "dry-release": "release-it --dry-run", - "prerelease": "tar --exclude=node_modules -zcvf generator-volto.tgz .", "release": "release-it", "release-major-alpha": "release-it major --preRelease=alpha", "release-alpha": "release-it --preRelease=alpha" @@ -75,10 +74,7 @@ "github": { "release": true, "releaseName": "@plone/generator-volto ${version}", - "releaseNotes": "node ../scripts/changelogupdater.cjs excerpt", - "assets": [ - "*.tgz" - ] + "releaseNotes": "node ../scripts/changelogupdater.cjs excerpt" } }, "jest": { From 371b515aa0d161bc9f2efbf199aaf40487df743f Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Wed, 16 Nov 2022 10:25:09 +0100 Subject: [PATCH 020/326] Release generate-volto 6.0.0-alpha.3 --- packages/generator-volto/CHANGELOG.md | 8 +------- packages/generator-volto/package.json | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 3937716d67..f04bf3706e 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,17 +1,11 @@ # Change Log -## 6.0.0 (unreleased) - -### Breaking - -### Feature +## 6.0.0-alpha.3 (2022-11-16) ### Bugfix - Add missing `"@plone/scripts": "^2.1.2"` devDependency to app template @sneridagh -### Internal - ## 6.0.0-alpha.2 (2022-11-15) ### Bugfix diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index 0ab2e73031..9f58c6f975 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "6.0.0-alpha.2", + "version": "6.0.0-alpha.3", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" From c5deacd1e681e9c407c3315ffcb23b4097c8e3b4 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Wed, 16 Nov 2022 10:25:15 +0100 Subject: [PATCH 021/326] Back to development (generator-volto) --- packages/generator-volto/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index f04bf3706e..27425baf85 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## 6.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + ## 6.0.0-alpha.3 (2022-11-16) ### Bugfix From e05e4a1083fef89dbd0ae642e5f6a6e41f0c165f Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Wed, 16 Nov 2022 16:34:01 +0100 Subject: [PATCH 022/326] Add `@plone/scripts` as a mandatory devDependency for projects to the upgrade guide --- CHANGELOG.md | 2 ++ docs/source/upgrade-guide/index.md | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 928196fed6..908f18a1ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### Documentation +- Add `@plone/scripts` as a mandatory devDependency for projects to the upgrade guide @sneridagh + ## 16.0.0-alpha.50 (2022-11-15) ### Feature diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 2987e66fc9..5fb42fd327 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -310,10 +310,11 @@ We are updating Volto to be able to use it, however some changes have to be made Remember to update your CI scripts accordingly. -4. It doesn't allow to use commands not declared as direct dependencies, so in your projects you should add `razzle` as a dependency: +4. It doesn't allow to use commands not declared as direct dependencies, so in your projects you should add `razzle` and `@plone/scripts` as development dependencies: ```diff devDependencies: { + + "@plone/scripts": "^2.1.2", + "razzle": "4.2.17", ``` From f66ab8ae0b8d54d7767916ac958b555a296744ef Mon Sep 17 00:00:00 2001 From: Jefferson Bledsoe Date: Wed, 16 Nov 2022 15:45:41 +0000 Subject: [PATCH 023/326] Ignore /.tool-versions (#3900) * Ignore /.tool-versions * Changelog --- .gitignore | 1 + CHANGELOG.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index cec0add449..18ffb13b0d 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,4 @@ public/critical.css # Sphinx and MyST docs/_build/ /.python-version +/.tool-versions diff --git a/CHANGELOG.md b/CHANGELOG.md index 908f18a1ab..3589424690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ ### Internal +- Ignore `.tool-versions` file + ### Documentation - Add `@plone/scripts` as a mandatory devDependency for projects to the upgrade guide @sneridagh From b3991bf0194e55b54bd975cbfe233e5a66739789 Mon Sep 17 00:00:00 2001 From: David Glick Date: Wed, 16 Nov 2022 22:32:47 -0800 Subject: [PATCH 024/326] Minor dependency updates (#3907) --- CHANGELOG.md | 1 + yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3589424690..c75319c4a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Internal - Ignore `.tool-versions` file +- Minor updates to dependencies ### Documentation diff --git a/yarn.lock b/yarn.lock index e8e6519b32..b65d140f89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9445,9 +9445,9 @@ __metadata: linkType: hard "deep-object-diff@npm:^1.1.0": - version: 1.1.7 - resolution: "deep-object-diff@npm:1.1.7" - checksum: 543fb1ae87b138ad260691e6949e72bf7dc144825084b7ad1886bb725d2ace1c19ed1ef1280f1116243e86bf2c6b942f45c670958b1468f644613f28c5dc97ea + version: 1.1.9 + resolution: "deep-object-diff@npm:1.1.9" + checksum: ecd42455e4773f653595d28070295e7aaa8402db5f8ab21d0bec115a7cb4de5e207a5665514767da5f025c96597f1d3a0a4888aeb4dd49e03c996871a3aa05ef languageName: node linkType: hard @@ -15944,24 +15944,24 @@ __metadata: linkType: hard "loader-utils@npm:^1.0.3, loader-utils@npm:^1.1.0, loader-utils@npm:^1.2.3, loader-utils@npm:^1.4.0": - version: 1.4.0 - resolution: "loader-utils@npm:1.4.0" + version: 1.4.2 + resolution: "loader-utils@npm:1.4.2" dependencies: big.js: ^5.2.2 emojis-list: ^3.0.0 json5: ^1.0.1 - checksum: d150b15e7a42ac47d935c8b484b79e44ff6ab4c75df7cc4cb9093350cf014ec0b17bdb60c5d6f91a37b8b218bd63b973e263c65944f58ca2573e402b9a27e717 + checksum: eb6fb622efc0ffd1abdf68a2022f9eac62bef8ec599cf8adb75e94d1d338381780be6278534170e99edc03380a6d29bc7eb1563c89ce17c5fed3a0b17f1ad804 languageName: node linkType: hard "loader-utils@npm:^2.0.0": - version: 2.0.2 - resolution: "loader-utils@npm:2.0.2" + version: 2.0.4 + resolution: "loader-utils@npm:2.0.4" dependencies: big.js: ^5.2.2 emojis-list: ^3.0.0 json5: ^2.1.2 - checksum: 9078d1ed47cadc57f4c6ddbdb2add324ee7da544cea41de3b7f1128e8108fcd41cd3443a85b7ee8d7d8ac439148aa221922774efe4cf87506d4fb054d5889303 + checksum: a5281f5fff1eaa310ad5e1164095689443630f3411e927f95031ab4fb83b4a98f388185bb1fe949e8ab8d4247004336a625e9255c22122b815bb9a4c5d8fc3b7 languageName: node linkType: hard From b75e118ff97e5375d232cdad3e00702dda034a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Thu, 17 Nov 2022 09:40:13 +0100 Subject: [PATCH 025/326] Improvement of the `ContentsBreadcrumbs` component (#3903) * Improvement of the `ContentsBreadcrumbs` component * Update src/components/manage/Contents/ContentsBreadcrumbs.jsx Co-authored-by: David Glick * Fix typo * locales Co-authored-by: David Glick --- CHANGELOG.md | 4 ++++ locales/ca/LC_MESSAGES/volto.po | 2 ++ locales/de/LC_MESSAGES/volto.po | 2 ++ locales/en/LC_MESSAGES/volto.po | 2 ++ locales/es/LC_MESSAGES/volto.po | 2 ++ locales/eu/LC_MESSAGES/volto.po | 2 ++ locales/fr/LC_MESSAGES/volto.po | 2 ++ locales/it/LC_MESSAGES/volto.po | 2 ++ locales/ja/LC_MESSAGES/volto.po | 2 ++ locales/nl/LC_MESSAGES/volto.po | 2 ++ locales/pt/LC_MESSAGES/volto.po | 2 ++ locales/pt_BR/LC_MESSAGES/volto.po | 2 ++ locales/ro/LC_MESSAGES/volto.po | 2 ++ locales/volto.pot | 4 +++- .../manage/Contents/ContentsBreadcrumbs.jsx | 10 +++++++--- .../Contents/ContentsBreadcrumbsHomeItem.jsx | 16 ++++++++++++++++ .../Contents/ContentsBreadcrumbsRootItem.jsx | 16 ++++++++++++++++ ...ontentsBreadcrumbs.Multilingual.test.jsx.snap | 4 ++-- 18 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/components/manage/Contents/ContentsBreadcrumbsHomeItem.jsx create mode 100644 src/components/manage/Contents/ContentsBreadcrumbsRootItem.jsx diff --git a/CHANGELOG.md b/CHANGELOG.md index c75319c4a9..b0580d64d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,12 @@ ### Breaking +- The `ContentsBreadcrumbs` component now renders the whole language name of the language root folder (if any) instead of just the `id` (before: `de`, now: `Deutsch`) @sneridagh + ### Feature +- Improvement of the `ContentsBreadcrumbs` component, add child `ContentsBreadcrumbsRootItem` and `ContentsBreadcrumbsHomeItem` for easy customization of these single elements in projects @sneridagh + ### Bugfix ### Internal diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index dd25cc2e1f..d0f46eb8df 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -1578,6 +1578,7 @@ msgstr "Historial de {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2707,6 +2708,7 @@ msgid "Roles" msgstr "Funcions" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Arrel" diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index f404a5acc2..63e8a9ec1d 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -1575,6 +1575,7 @@ msgstr "Historie von {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2704,6 +2705,7 @@ msgid "Roles" msgstr "Rollen" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Wurzel" diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 55e509c283..a2e3a07ed0 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -1569,6 +1569,7 @@ msgstr "" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2698,6 +2699,7 @@ msgid "Roles" msgstr "" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index ad97545ef3..741be6533a 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -1580,6 +1580,7 @@ msgstr "Historial de {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2709,6 +2710,7 @@ msgid "Roles" msgstr "Roles" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Raíz" diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index c9b160bcb2..4a831b505a 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -1576,6 +1576,7 @@ msgstr "{title} elementuaren historia" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2705,6 +2706,7 @@ msgid "Roles" msgstr "Rolak" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Erroa" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index a16ebbb724..496bab24cc 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -1586,6 +1586,7 @@ msgstr "Historique de {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2715,6 +2716,7 @@ msgid "Roles" msgstr "Rôles" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index 91577d0383..1c85837d03 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -1569,6 +1569,7 @@ msgstr "Cronologia di {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2698,6 +2699,7 @@ msgid "Roles" msgstr "Ruoli" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Radice" diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index c378f1e989..d4c8a92a32 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -1577,6 +1577,7 @@ msgstr "{title} の履歴" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2706,6 +2707,7 @@ msgid "Roles" msgstr "ロール" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Root" diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index 2acaa44643..37b8c877ee 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -1576,6 +1576,7 @@ msgstr "Geschiedenis van {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2705,6 +2706,7 @@ msgid "Roles" msgstr "Rollen" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Root" diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index a23fc0d80c..d7c83d2f82 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -1577,6 +1577,7 @@ msgstr "Histórico de {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2706,6 +2707,7 @@ msgid "Roles" msgstr "Papéis" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "" diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index 30a03d5bb7..185ec0c24a 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -1579,6 +1579,7 @@ msgstr "Histórico de {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2708,6 +2709,7 @@ msgid "Roles" msgstr "Papéis" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Raiz" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index b38bf92850..f4280d7b4f 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -1569,6 +1569,7 @@ msgstr "Istoricul {title}" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2698,6 +2699,7 @@ msgid "Roles" msgstr "Roluri" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "Root" diff --git a/locales/volto.pot b/locales/volto.pot index cf2612dadf..c510dbd659 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2022-11-09T21:02:11.332Z\n" +"POT-Creation-Date: 2022-11-16T16:07:48.403Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -1571,6 +1571,7 @@ msgstr "" #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs # defaultMessage: Home msgid "Home" @@ -2700,6 +2701,7 @@ msgid "Roles" msgstr "" #: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsRootItem # defaultMessage: Root msgid "Root" msgstr "" diff --git a/src/components/manage/Contents/ContentsBreadcrumbs.jsx b/src/components/manage/Contents/ContentsBreadcrumbs.jsx index 0f386ec0c8..417d8338d7 100644 --- a/src/components/manage/Contents/ContentsBreadcrumbs.jsx +++ b/src/components/manage/Contents/ContentsBreadcrumbs.jsx @@ -2,6 +2,10 @@ import React from 'react'; import { Breadcrumb } from 'semantic-ui-react'; import { Link, useLocation } from 'react-router-dom'; import { defineMessages, useIntl } from 'react-intl'; +import { langmap } from '@plone/volto/helpers'; +import ContentsBreadcrumbsRootItem from '@plone/volto/components/manage/Contents/ContentsBreadcrumbsRootItem'; +import ContentsBreadcrumbsHomeItem from '@plone/volto/components/manage/Contents/ContentsBreadcrumbsHomeItem'; + import config from '@plone/volto/registry'; const messages = defineMessages({ @@ -31,7 +35,7 @@ const ContentsBreadcrumbs = (props) => { className="section" title={intl.formatMessage(messages.root)} > - {intl.formatMessage(messages.root)} + @@ -42,7 +46,7 @@ const ContentsBreadcrumbs = (props) => { className="section" title={intl.formatMessage(messages.home)} > - {lang} + {langmap?.[lang]?.nativeName ?? lang} )} {!settings.isMultilingual && ( @@ -51,7 +55,7 @@ const ContentsBreadcrumbs = (props) => { className="section" title={intl.formatMessage(messages.home)} > - {intl.formatMessage(messages.home)} + )} {items.map((breadcrumb, index, breadcrumbs) => [ diff --git a/src/components/manage/Contents/ContentsBreadcrumbsHomeItem.jsx b/src/components/manage/Contents/ContentsBreadcrumbsHomeItem.jsx new file mode 100644 index 0000000000..f8b97546c0 --- /dev/null +++ b/src/components/manage/Contents/ContentsBreadcrumbsHomeItem.jsx @@ -0,0 +1,16 @@ +import { defineMessages, useIntl } from 'react-intl'; + +const messages = defineMessages({ + home: { + id: 'Home', + defaultMessage: 'Home', + }, +}); + +const ContentsBreadcrumbsHomeItem = () => { + const intl = useIntl(); + + return <>{intl.formatMessage(messages.home)}; +}; + +export default ContentsBreadcrumbsHomeItem; diff --git a/src/components/manage/Contents/ContentsBreadcrumbsRootItem.jsx b/src/components/manage/Contents/ContentsBreadcrumbsRootItem.jsx new file mode 100644 index 0000000000..ce88f4f063 --- /dev/null +++ b/src/components/manage/Contents/ContentsBreadcrumbsRootItem.jsx @@ -0,0 +1,16 @@ +import { defineMessages, useIntl } from 'react-intl'; + +const messages = defineMessages({ + root: { + id: 'Root', + defaultMessage: 'Root', + }, +}); + +const ContentsBreadcrumbsRootItem = () => { + const intl = useIntl(); + + return <>{intl.formatMessage(messages.root)}; +}; + +export default ContentsBreadcrumbsRootItem; diff --git a/src/components/manage/Contents/__snapshots__/ContentsBreadcrumbs.Multilingual.test.jsx.snap b/src/components/manage/Contents/__snapshots__/ContentsBreadcrumbs.Multilingual.test.jsx.snap index d681639910..2ec2f635c8 100644 --- a/src/components/manage/Contents/__snapshots__/ContentsBreadcrumbs.Multilingual.test.jsx.snap +++ b/src/components/manage/Contents/__snapshots__/ContentsBreadcrumbs.Multilingual.test.jsx.snap @@ -23,7 +23,7 @@ exports[`ContentsBreadcrumbs Multilingual renders a ContentsBreadcrumbs componen onClick={[Function]} title="Home" > - en + English
- en + English
Date: Thu, 17 Nov 2022 10:17:05 +0100 Subject: [PATCH 026/326] Add german translations (#3905) * Translate toast message "Membership updated" * Add and correct german translations --- CHANGELOG.md | 2 ++ locales/ca/LC_MESSAGES/volto.po | 5 +++ locales/de/LC_MESSAGES/volto.po | 33 +++++++++++-------- locales/en/LC_MESSAGES/volto.po | 5 +++ locales/es/LC_MESSAGES/volto.po | 5 +++ locales/eu/LC_MESSAGES/volto.po | 5 +++ locales/fr/LC_MESSAGES/volto.po | 5 +++ locales/it/LC_MESSAGES/volto.po | 5 +++ locales/ja/LC_MESSAGES/volto.po | 5 +++ locales/nl/LC_MESSAGES/volto.po | 5 +++ locales/pt/LC_MESSAGES/volto.po | 5 +++ locales/pt_BR/LC_MESSAGES/volto.po | 5 +++ locales/ro/LC_MESSAGES/volto.po | 5 +++ locales/volto.pot | 7 +++- .../Users/UserGroupMembershipListing.jsx | 4 +-- src/helpers/MessageLabels/MessageLabels.js | 4 +++ 16 files changed, 88 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0580d64d4..20953b0214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ ### Feature - Improvement of the `ContentsBreadcrumbs` component, add child `ContentsBreadcrumbsRootItem` and `ContentsBreadcrumbsHomeItem` for easy customization of these single elements in projects @sneridagh +- Add german translation for group membership panel. @ksuess +- Fix general german translations: Address user polite. Correct 'listing template' to 'listing variant'. Add missing translations. @ksuess ### Bugfix diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index d0f46eb8df..2bb52e2de4 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -1998,6 +1998,11 @@ msgstr "El valor màxim és {len}." msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index 63e8a9ec1d..3d79cb8537 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -202,12 +202,12 @@ msgstr "Zu Gruppe hinzufügen" #: helpers/MessageLabels/MessageLabels # defaultMessage: Add users to group msgid "Add users to group" -msgstr "Füge Benutzer zu Gruppe hinzu" +msgstr "Benutzer zu Gruppe hinzufügen" #: components/manage/Widgets/VocabularyTermsWidget # defaultMessage: Add term msgid "Add vocabulary term" -msgstr "Füge neuen Term hinzu" +msgstr "Neuen Term hinzufügen" #: components/manage/Add/Add # defaultMessage: Add {type} @@ -634,7 +634,7 @@ msgstr "Kollektion" #: components/manage/Widgets/ColorPickerWidget # defaultMessage: Color msgid "Color" -msgstr "" +msgstr "Farbe" #: components/manage/Controlpanels/ModerateComments #: components/theme/Comments/CommentEditModal @@ -1540,7 +1540,7 @@ msgstr "Überschrift" #: components/manage/Blocks/Listing/schema # defaultMessage: Headline level msgid "Headline level" -msgstr "" +msgstr "Überschrift" #: components/manage/Blocks/Search/schema # defaultMessage: Hidden facets will still filter the results if proper parameters are passed in URLs @@ -1995,6 +1995,11 @@ msgstr "Maximaler Wert ist {len}" msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "Gruppenmitgliedschaft aktualisiert" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" @@ -2074,12 +2079,12 @@ msgstr "" #: components/manage/Blocks/Search/schema # defaultMessage: Multiple choices? msgid "Multiple choices?" -msgstr "Mehrfachauswahl" +msgstr "Mehrfachauswahl?" #: components/theme/PasswordReset/PasswordReset # defaultMessage: My email is msgid "My email is" -msgstr "" +msgstr "Meine E-Mail ist" #: components/theme/PasswordReset/PasswordReset # defaultMessage: My username is @@ -2556,7 +2561,7 @@ msgstr "Registrierungsformular" #: components/theme/Search/Search # defaultMessage: Relevance msgid "Relevance" -msgstr "relevanz" +msgstr "Relevanz" #: components/manage/Aliases/Aliases # defaultMessage: Remove @@ -2665,7 +2670,7 @@ msgstr "Ergebnisvorschau" #: components/manage/Blocks/Search/SearchBlockEdit # defaultMessage: Results template msgid "Results template" -msgstr "Template Suchergebnisliste" +msgstr "Variante Suchergebnisliste" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Reversed order @@ -2899,7 +2904,7 @@ msgstr "Auswahl" #: components/manage/Widgets/TokenWidget # defaultMessage: Select… msgid "Select…" -msgstr "Wähle…" +msgstr "Auswahl" #: components/theme/ContactForm/ContactForm # defaultMessage: Send @@ -3168,7 +3173,7 @@ msgstr "Betreff" #: helpers/MessageLabels/MessageLabels # defaultMessage: Success msgid "Success" -msgstr "Erfolgreich" +msgstr "Erfolg" #: components/manage/Controlpanels/UndoControlpanel # defaultMessage: Successfully undone transactions @@ -3350,7 +3355,7 @@ msgstr "" #: components/manage/Form/InlineForm # defaultMessage: There were some errors msgid "There were some errors" -msgstr "Es sind Fehler" +msgstr "Es gibt Fehler" #: components/manage/Form/ModalForm #: helpers/MessageLabels/MessageLabels @@ -3983,7 +3988,7 @@ msgstr "" #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" -msgstr "" +msgstr "Verfügbare Ansichten" #: components/theme/Login/Login #: components/theme/PasswordReset/RequestPasswordReset @@ -4233,7 +4238,7 @@ msgstr "Veröffentlicht" #: components/manage/Widgets/QueryWidget # defaultMessage: Select… msgid "querystring-widget-select" -msgstr "Select Auswahlfeld" +msgstr "Auswahl" #: components/theme/Search/Search # defaultMessage: results @@ -4483,7 +4488,7 @@ msgstr "Video" #: components/manage/Blocks/Search/schema # defaultMessage: Views msgid "views" -msgstr "" +msgstr "Ansichten" #: components/theme/EventDetails/EventDetails # defaultMessage: Visit external website diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index a2e3a07ed0..232d9d95d1 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -1989,6 +1989,11 @@ msgstr "" msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index 741be6533a..b2a8c27d59 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -2000,6 +2000,11 @@ msgstr "El valor máximo es {len}." msgid "Medium" msgstr "Mediano" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index 4a831b505a..a78f500fb5 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -1996,6 +1996,11 @@ msgstr "Balio handiena {len} da." msgid "Medium" msgstr "Ertaina" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 496bab24cc..2dc6f9d9cd 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -2006,6 +2006,11 @@ msgstr "" msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index 1c85837d03..fa076a7f19 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -1989,6 +1989,11 @@ msgstr "Il valore massimo è {len}." msgid "Medium" msgstr "Medio" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index d4c8a92a32..36cd4c2d0f 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -1997,6 +1997,11 @@ msgstr "最大の値は {len} です。" msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index 37b8c877ee..8deeeb573d 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -1996,6 +1996,11 @@ msgstr "Maximum waarde is {len}" msgid "Medium" msgstr "Medium" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index d7c83d2f82..8d853ba007 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -1997,6 +1997,11 @@ msgstr "" msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index 185ec0c24a..602b7c4a78 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -1999,6 +1999,11 @@ msgstr "O valor máximo é {len}." msgid "Medium" msgstr "Médio" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index f4280d7b4f..c12f29b67c 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -1989,6 +1989,11 @@ msgstr "Valoarea maximă este {len}." msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/locales/volto.pot b/locales/volto.pot index c510dbd659..61bd009b74 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2022-11-16T16:07:48.403Z\n" +"POT-Creation-Date: 2022-11-16T16:45:46.315Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -1991,6 +1991,11 @@ msgstr "" msgid "Medium" msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated +msgid "Membership updated" +msgstr "" + #: components/theme/ContactForm/ContactForm # defaultMessage: Message msgid "Message" diff --git a/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx b/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx index 7755633193..905f21a92b 100644 --- a/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx +++ b/src/components/manage/Controlpanels/Users/UserGroupMembershipListing.jsx @@ -144,7 +144,7 @@ const ListingTemplate = ({ , ); }); @@ -178,7 +178,7 @@ const ListingTemplate = ({ , ); }); diff --git a/src/helpers/MessageLabels/MessageLabels.js b/src/helpers/MessageLabels/MessageLabels.js index 99769cac68..b77806d326 100644 --- a/src/helpers/MessageLabels/MessageLabels.js +++ b/src/helpers/MessageLabels/MessageLabels.js @@ -190,6 +190,10 @@ export const messages = defineMessages({ id: 'User Group Membership', defaultMessage: 'User Group Membership', }, + membershipUpdated: { + id: 'Membership updated', + defaultMessage: 'Membership updated', + }, noUserFound: { id: 'No user found', defaultMessage: 'No user found', From 0de457ddeb6201a236908a0df6dce8c2ba11db7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Thu, 17 Nov 2022 15:31:18 +0100 Subject: [PATCH 027/326] Update Cypress 11 (#3909) * Update Cypress 11 * Add further notice of this upgrade --- CHANGELOG.md | 1 + docs/source/upgrade-guide/index.md | 4 ++++ package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20953b0214..ef4c39090d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Ignore `.tool-versions` file - Minor updates to dependencies +- Update Cypress 11 @sneridagh ### Documentation diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 5fb42fd327..2cf382bf87 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -349,6 +349,10 @@ Could be that forcing your project to use older versions might still work with o See https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-version-10-0 for more information. +```{note} +Later on, the core has been upgraded to Cypress 11, however no changes to be made if you already upgraded to Cypress 10. +``` + ### The complete configuration registry is passed to the add-ons and the project configuration pipeline The core in versions prior Volto 16.0.0-alpha.22 was passing a simplified version of the configuration registry (in fact, a plain object primitive) to the add-on list and project configuration pipeline. diff --git a/package.json b/package.json index 0ce55baf09..d535041b48 100644 --- a/package.json +++ b/package.json @@ -231,7 +231,7 @@ "@loadable/server": "5.14.0", "@loadable/webpack-plugin": "5.14.0", "@plone/scripts": "2.1.2", - "@testing-library/cypress": "8.0.3", + "@testing-library/cypress": "8.0.7", "@testing-library/jest-dom": "5.16.4", "@testing-library/react": "12.1.5", "@testing-library/react-hooks": "8.0.1", @@ -250,7 +250,7 @@ "connected-react-router": "6.8.0", "crypto-random-string": "3.2.0", "css-loader": "5.2.7", - "cypress": "10.9.0", + "cypress": "11.1.0", "cypress-axe": "1.0.0", "cypress-file-upload": "5.0.8", "debug": "4.3.2", diff --git a/yarn.lock b/yarn.lock index b65d140f89..b6c17abd6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2752,7 +2752,7 @@ __metadata: "@storybook/addon-essentials": ^6.3.0 "@storybook/addon-links": ^6.3.0 "@storybook/react": ^6.3.0 - "@testing-library/cypress": 8.0.3 + "@testing-library/cypress": 8.0.7 "@testing-library/jest-dom": 5.16.4 "@testing-library/react": 12.1.5 "@testing-library/react-hooks": 8.0.1 @@ -2772,7 +2772,7 @@ __metadata: connected-react-router: 6.8.0 crypto-random-string: 3.2.0 css-loader: 5.2.7 - cypress: 10.9.0 + cypress: 11.1.0 cypress-axe: 1.0.0 cypress-file-upload: 5.0.8 debug: 4.3.2 @@ -4495,15 +4495,15 @@ __metadata: languageName: node linkType: hard -"@testing-library/cypress@npm:8.0.3": - version: 8.0.3 - resolution: "@testing-library/cypress@npm:8.0.3" +"@testing-library/cypress@npm:8.0.7": + version: 8.0.7 + resolution: "@testing-library/cypress@npm:8.0.7" dependencies: "@babel/runtime": ^7.14.6 "@testing-library/dom": ^8.1.0 peerDependencies: - cypress: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 - checksum: 319f6c7297f85e5673882a71009b5d5b576006f986d87a6ca6f2f8c679d96d9779f3ddbc01654b5a943407fa91192814f250835ce01f153e33ef24818cf1bf20 + cypress: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + checksum: e005bc1a7ec808706c57e95ed312069fb5be39ea7362900dc2a32c09f124d478ade69ebcd7df88c076e3867ab328ae6e6ce13791bdf042621ff66b56552bf74b languageName: node linkType: hard @@ -9195,9 +9195,9 @@ __metadata: languageName: node linkType: hard -"cypress@npm:10.9.0": - version: 10.9.0 - resolution: "cypress@npm:10.9.0" +"cypress@npm:11.1.0": + version: 11.1.0 + resolution: "cypress@npm:11.1.0" dependencies: "@cypress/request": ^2.88.10 "@cypress/xvfb": ^1.2.4 @@ -9243,7 +9243,7 @@ __metadata: yauzl: ^2.10.0 bin: cypress: bin/cypress - checksum: 79e3dccbb82d0b0c92bb8888682766a9738374f473cf2e9a04825a6b3dbe4420c57948cabaf757af0929192a8e61b7f08b32d7f6ee0c3cddf2736cf4f408edc1 + checksum: ee0097778cf3cdf3854325cabf19a60a7486d46ae70082034c05b22b203f21ff85a4871c08dadc6641be649a64c739a443dd3f2d6a5ab112fc9ead703e1f1be3 languageName: node linkType: hard From 309cec81773b8d04c20ba68e66d74ab324b5e18b Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Fri, 18 Nov 2022 12:04:32 +0200 Subject: [PATCH 028/326] Objectwidget defaultvalues (#3911) * Fixes to objectwidget and related stories * Storybook, changelog * Storybook, changelog * Set a value based on default in the Field component; remove story, break NumberWidget compatibility by droping the defaultValue prop; fix faulty snapshot for InlineForm test * Also support defaultValue to be less breaking; changelog --- CHANGELOG.md | 3 + src/components/manage/Form/Field.jsx | 12 +++- .../__snapshots__/InlineForm.test.jsx.snap | 2 +- .../manage/Widgets/NumberWidget.jsx | 3 +- .../Widgets/ObjectListWidget.stories.js | 72 +++++++++++-------- .../manage/Widgets/ObjectWidget.jsx | 3 +- .../manage/Widgets/ObjectWidget.stories.jsx | 21 +++++- .../__snapshots__/NumberWidget.test.jsx.snap | 1 + 8 files changed, 80 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef4c39090d..0a893e0c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,12 @@ - Improvement of the `ContentsBreadcrumbs` component, add child `ContentsBreadcrumbsRootItem` and `ContentsBreadcrumbsHomeItem` for easy customization of these single elements in projects @sneridagh - Add german translation for group membership panel. @ksuess - Fix general german translations: Address user polite. Correct 'listing template' to 'listing variant'. Add missing translations. @ksuess +- All Fields now understand the `default` prop as a fallback value in case their data value is missing. As a convenience, the `defaultValue` is also used as a fallback, but this shouldn't proliferate. @tiberiuichim ### Bugfix +- Fix ObjectWidget handling of `default` values coming from schemas. @tiberiuichim + ### Internal - Ignore `.tool-versions` file diff --git a/src/components/manage/Form/Field.jsx b/src/components/manage/Form/Field.jsx index f509903237..7e8cf9df8d 100644 --- a/src/components/manage/Form/Field.jsx +++ b/src/components/manage/Form/Field.jsx @@ -232,9 +232,17 @@ const DndConnectedField = injectLazyLibs(['reactDnd'])(UnconnectedField); const Field = (props) => props.onOrder ? ( - + ) : ( - + ); /** diff --git a/src/components/manage/Form/__snapshots__/InlineForm.test.jsx.snap b/src/components/manage/Form/__snapshots__/InlineForm.test.jsx.snap index a22c384477..aaec8a7ab3 100644 --- a/src/components/manage/Form/__snapshots__/InlineForm.test.jsx.snap +++ b/src/components/manage/Form/__snapshots__/InlineForm.test.jsx.snap @@ -66,7 +66,7 @@ exports[`Form renders a form component with defaults in the schema - Checkboxes - No description - - No value + false
diff --git a/src/components/manage/Widgets/NumberWidget.jsx b/src/components/manage/Widgets/NumberWidget.jsx index bfc724f042..d68c082b2c 100644 --- a/src/components/manage/Widgets/NumberWidget.jsx +++ b/src/components/manage/Widgets/NumberWidget.jsx @@ -28,7 +28,6 @@ const NumberWidget = (props) => { onChange, onBlur, onClick, - defaultValue, isDisabled, maximum, minimum, @@ -44,7 +43,7 @@ const NumberWidget = (props) => { disabled={isDisabled} min={minimum || null} max={maximum || null} - value={value ?? defaultValue} + value={value} placeholder={placeholder} onChange={({ target }) => onChange(id, target.value === '' ? undefined : target.value) diff --git a/src/components/manage/Widgets/ObjectListWidget.stories.js b/src/components/manage/Widgets/ObjectListWidget.stories.js index 343c38ed32..72518ce27e 100644 --- a/src/components/manage/Widgets/ObjectListWidget.stories.js +++ b/src/components/manage/Widgets/ObjectListWidget.stories.js @@ -3,6 +3,8 @@ import { RealStoreWrapper, FormUndoWrapper } from '@plone/volto/storybook'; import React from 'react'; import { searchResults } from './ObjectBrowserWidget.stories'; +import { cloneDeep } from 'lodash'; + const defaultSchema = { title: 'Item', addMessage: 'Add item', @@ -105,7 +107,7 @@ const customStore = { const ObjectListWidgetComponent = ({ children, secondarySchema, - enableSchemaExtender, + schemaExtender, ...args }) => { return ( @@ -124,33 +126,7 @@ const ObjectListWidgetComponent = ({ block="testBlock" value={state.value} onChange={(block, value) => onChange({ value })} - schemaExtender={ - enableSchemaExtender && - ((schema, data, intl) => { - const finalSchema = - data?.href?.[0]?.['@id'] === '/image' - ? { - ...schema, - fieldsets: [ - { - ...schema.fieldsets[0], - fields: [ - ...schema.fieldsets[0].fields, - ...secondarySchema.fieldsets[0].fields, - ], - }, - ...schema.fieldsets.slice(1), - ...secondarySchema.fieldsets.slice(1), - ], - properties: { - ...schema.properties, - ...secondarySchema.properties, - }, - } - : schema; - return finalSchema; - }) - } + schemaExtender={schemaExtender} />
Value: {JSON.stringify(state.value, null, 4)}
@@ -171,6 +147,19 @@ MultipleFieldsets.args = { schema: multiFieldsetSchema, }; +const addDefaultValues = (schema) => { + schema = cloneDeep(schema); + schema.properties.title.default = 'Plone release announcement'; + schema.properties.description.default = + 'Soon to arrive on your local machine'; + return schema; +}; + +export const DefaultValues = ObjectListWidgetComponent.bind({}); +DefaultValues.args = { + schema: addDefaultValues(multiFieldsetSchema), +}; + const defaultSecondarySchema = { title: 'Additional fields', fieldsets: [ @@ -189,8 +178,33 @@ const defaultSecondarySchema = { }; export const SchemaExtender = (args) => { + const { secondarySchema } = args; + const schemaExtender = (schema, data, intl) => { + const finalSchema = + data?.href?.[0]?.['@id'] === '/image' + ? { + ...schema, + fieldsets: [ + { + ...schema.fieldsets[0], + fields: [ + ...schema.fieldsets[0].fields, + ...secondarySchema.fieldsets[0].fields, + ], + }, + ...schema.fieldsets.slice(1), + ...secondarySchema.fieldsets.slice(1), + ], + properties: { + ...schema.properties, + ...secondarySchema.properties, + }, + } + : schema; + return finalSchema; + }; return ( - + <> Notice the form changes if you pick "I am an image" for the{' '} source field. We're achieving that by passing a custom{' '} diff --git a/src/components/manage/Widgets/ObjectWidget.jsx b/src/components/manage/Widgets/ObjectWidget.jsx index 28b3915a74..2228416805 100644 --- a/src/components/manage/Widgets/ObjectWidget.jsx +++ b/src/components/manage/Widgets/ObjectWidget.jsx @@ -34,14 +34,13 @@ const FieldSet = ({ id, }) => { return data.fields.map((field, idx) => { - const v = value?.[field] || schema.properties[field].defaultValue; return ( { diff --git a/src/components/manage/Widgets/ObjectWidget.stories.jsx b/src/components/manage/Widgets/ObjectWidget.stories.jsx index 7e9b634f66..006d395995 100644 --- a/src/components/manage/Widgets/ObjectWidget.stories.jsx +++ b/src/components/manage/Widgets/ObjectWidget.stories.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { searchResults } from './ObjectBrowserWidget.stories'; +import { cloneDeep } from 'lodash'; import ObjectWidget from './ObjectWidget'; import WidgetStory from './story'; @@ -63,7 +64,7 @@ const multipleFieldsets = { contributors: { title: 'Contributors', description: 'Example field with contributors information', - wiget: 'textarea', + widget: 'textarea', }, href: { title: 'Source', @@ -93,6 +94,15 @@ const multipleFieldsets = { required: [], }; +const addDefaultValues = (schema) => { + schema = cloneDeep(schema); + schema.properties.title.default = 'Plone release announcement'; + schema.properties.description.default = + 'Soon to arrive on your local machine'; + schema.properties.contributors.default = 'Plone team\nVolto team'; + return schema; +}; + const customStore = { search: { subrequests: { @@ -125,6 +135,15 @@ MultipleFieldsets.args = { schema: multipleFieldsets, }; +export const DefaultValue = WidgetStory.bind({ + props: { id: 'objectwidget', title: 'Slides' }, + widget: ObjectWidget, + customStore, +}); +DefaultValue.args = { + schema: addDefaultValues(multipleFieldsets), +}; + export default { title: 'Edit Widgets/Object (JSON)', component: ObjectWidget, diff --git a/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap b/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap index b9bf751dd9..37be89be37 100644 --- a/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap +++ b/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap @@ -40,6 +40,7 @@ Array [ onChange={[Function]} onClick={[Function]} type="number" + value={null} /> From 76bd39c411bb3fc9c975631bd36f1697779b2cde Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Fri, 18 Nov 2022 07:11:54 -0300 Subject: [PATCH 029/326] Configure Jest's moduleNameMapper with AddonConfigurationRegistry (#3913) * gitignore * Configure Jest's moduleNameMapper with AddonConfigurationRegistry Thus, customizations made in Volto projects will be considered in the tests. --- .gitignore | 2 ++ CHANGELOG.md | 1 + package.json | 1 - razzle.config.js | 21 +++++++++++++++++---- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 18ffb13b0d..cdbc798738 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ lighthouse-report.html .vscode/ .#* *~ +/.settings/ +.*project # Python /api/.installed.cfg diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a893e0c61..6201ec1fd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Internal +- Configure Jest's `moduleNameMapper` with `AddonConfigurationRegistry`. Fix https://github.com/plone/volto/issues/3870 @wesleybl - Ignore `.tool-versions` file - Minor updates to dependencies - Update Cypress 11 @sneridagh diff --git a/package.json b/package.json index d535041b48..e218541575 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "moduleNameMapper": { "@plone/volto/package.json": "/package.json", "@plone/volto/(.*)$": "/src/$1", - "@plone/volto-slate": "/packages/volto-slate/src", "~/config": "/src/config", "~/../locales/${lang}.json": "/locales/en.json", "(.*)/locales/(.*)": "/locales/$2", diff --git a/razzle.config.js b/razzle.config.js index 02e3db88a9..0013fd8b27 100644 --- a/razzle.config.js +++ b/razzle.config.js @@ -21,6 +21,12 @@ const packageJson = require(path.join(projectRootPath, 'package.json')); const registry = new AddonConfigurationRegistry(projectRootPath); +const addonCustomizationPaths = registry.getAddonCustomizationPaths(); +const addonsFromEnvVarCustomizationPaths = registry.getAddonsFromEnvVarCustomizationPaths(); +const projectCustomizationPaths = registry.getProjectCustomizationPaths(); +const resolveAliases = registry.getResolveAliases(); + + const defaultModify = ({ env: { target, dev }, webpackConfig: config, @@ -203,14 +209,14 @@ const defaultModify = ({ ]; config.resolve.alias = { - ...registry.getAddonCustomizationPaths(), - ...registry.getAddonsFromEnvVarCustomizationPaths(), - ...registry.getProjectCustomizationPaths(), + ...addonCustomizationPaths, + ...addonsFromEnvVarCustomizationPaths, + ...projectCustomizationPaths, ...config.resolve.alias, '../../theme.config$': `${projectRootPath}/theme/theme.config`, 'volto-themes': `${registry.voltoPath}/theme/themes`, 'load-volto-addons': addonsLoaderPath, - ...registry.getResolveAliases(), + ...resolveAliases, '@plone/volto': `${registry.voltoPath}/src`, // to be able to reference path uncustomized by webpack '@plone/volto-original': `${registry.voltoPath}/src`, @@ -316,6 +322,13 @@ module.exports = { plugins, modifyJestConfig: ({ jestConfig }) => { jestConfig.testEnvironment = 'jsdom'; + jestConfig.moduleNameMapper = { + ...addonCustomizationPaths, + ...addonsFromEnvVarCustomizationPaths, + ...projectCustomizationPaths, + ...resolveAliases, + ...jestConfig.moduleNameMapper, + }; return jestConfig; }, modifyWebpackConfig: ({ From 4b4c65cfea6259775365d6e87882622483a9e68b Mon Sep 17 00:00:00 2001 From: Jefferson Bledsoe Date: Fri, 18 Nov 2022 10:30:35 +0000 Subject: [PATCH 030/326] Allow passing ariaHidden, id and style to an Icon's SVG (#3908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow passing arbitrary style and props to an Icon's SVG * Update snapshot * Only allow aria-hidden and id extra props * Changelog * Fix null appearing in attributes * changelog Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 1 + .../UndoControlpanel.test.jsx.snap | 1 + src/components/theme/Icon/Icon.jsx | 21 +++++++++++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6201ec1fd0..8434d5df6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Improvement of the `ContentsBreadcrumbs` component, add child `ContentsBreadcrumbsRootItem` and `ContentsBreadcrumbsHomeItem` for easy customization of these single elements in projects @sneridagh - Add german translation for group membership panel. @ksuess - Fix general german translations: Address user polite. Correct 'listing template' to 'listing variant'. Add missing translations. @ksuess +- Allow passing ariaHidden, id and style to an Icon's SVG @JeffersonBledsoe #3908 - All Fields now understand the `default` prop as a fallback value in case their data value is missing. As a convenience, the `defaultValue` is also used as a fallback, but this shouldn't proliferate. @tiberiuichim ### Bugfix diff --git a/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap b/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap index 4c6e2f95fe..5f3f74bc51 100644 --- a/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap +++ b/src/components/manage/Controlpanels/__snapshots__/UndoControlpanel.test.jsx.snap @@ -412,6 +412,7 @@ exports[`UndoControlpanel renders undo controlpanel component 1`] = ` "__html": "Undoundefined", } } + id="undo-button" onClick={[Function]} style={ Object { diff --git a/src/components/theme/Icon/Icon.jsx b/src/components/theme/Icon/Icon.jsx index caff8c422d..bf0fc2e872 100644 --- a/src/components/theme/Icon/Icon.jsx +++ b/src/components/theme/Icon/Icon.jsx @@ -32,13 +32,30 @@ const defaultSize = '36px'; * * for further reference see {@link https://kitconcept.com/blog/pastanaga-icon-system/ | here} */ -const Icon = ({ name, size, color, className, title, onClick }) => ( +const Icon = ({ + name, + size, + color, + className, + title, + onClick, + style = {}, + id, + ariaHidden, +}) => ( ${title}${name.content}` : name.content, }} From 6d5ac5fa9eee70d2315aa4bba7e28ab3b0a0d2c4 Mon Sep 17 00:00:00 2001 From: David Glick Date: Fri, 18 Nov 2022 02:55:52 -0800 Subject: [PATCH 031/326] Filter settings in control panels (#3904) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Partial filter function draft * Add schema.fieldsest update function and minor fix * Replace switch function with preconstructed obj * Edit fieldsets filter function * Add cypress draft for language-controlpanel * Add unwantedSettings at Controlpanel & minor fixes * CHANGELOG entry * Filter control panel schema in settings (#3914) * Filter control panel schema in settings * Add doc entry Co-authored-by: Tiberiu Ichim Co-authored-by: Dante Álvarez <89805481+danalvrz@users.noreply.github.com> Co-authored-by: Víctor Fernández de Alba Co-authored-by: Tiberiu Ichim --- CHANGELOG.md | 3 +- .../tests/core/basic/language-controlpanel.js | 26 +++++++++ .../configuration/settings-reference.md | 3 + .../manage/Controlpanels/Controlpanel.jsx | 6 +- src/config/ControlPanels.js | 58 +++++++++++++++++++ src/config/index.js | 3 +- test-setup-config.js | 6 +- 7 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 cypress/tests/core/basic/language-controlpanel.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 8434d5df6b..b15bb8ab1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ ### Bugfix +- Hide control panel settings that are not relevant to Volto @danalvrz - Fix ObjectWidget handling of `default` values coming from schemas. @tiberiuichim ### Internal @@ -73,7 +74,7 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa - Remove slate's builtin undo support, as it conflicts with Volto's undo manager. This fixes crashes when undoing in text blocks and slate's undo stack is empty and "crosses" into Volto's undo stack. This is a temporary workaround, ideally the two undo managers would be delimited so they each work together. @tiberiuichim - Fix highlighting of selection when the Slate editor is not DOM-focused. @tiberiuichim - Improve the algorithm that calculates the position of the Slate Toolbar @tiberiuichim -- The `_unwrapElement` of the volto-slate `ElementEditor` will return an updated range (selection) of the unwrapped element. @tiberiuichim +- The `_unwrapElement` of the volto-slate `ElementEditor` will return an updated range (selection) of the unwrapped element. @tiberiuichim - Replace the main client entry point in `start-client.jsx` anonymous function for a named one. @sneridagh - Fix `currentPath` option for `openObjectBrowser`. @iFlameing - Fix updating the listing block when the variation is changed while editing @tiberiuichim diff --git a/cypress/tests/core/basic/language-controlpanel.js b/cypress/tests/core/basic/language-controlpanel.js new file mode 100644 index 0000000000..fb734abde2 --- /dev/null +++ b/cypress/tests/core/basic/language-controlpanel.js @@ -0,0 +1,26 @@ +describe('Language control-panel', () => { + beforeEach(() => { + cy.visit('/'); + cy.autologin(); + cy.waitForResourceToLoad('@navigation'); + cy.waitForResourceToLoad('@breadcrumbs'); + cy.waitForResourceToLoad('@actions'); + }); + + it('Displays all valid fields in language control-panel', () => { + cy.visit('/controlpanel/language'); + cy.get('main').within(() => { + cy.get('.field-wrapper-default_language').should('be.visible'); + cy.get('.field-wrapper-available_languages').should('be.visible'); + cy.get('.field-wrapper-use_combined_language_codes').should('be.visible'); + }); + }); + + it('Does not display unwanted fields in language control-panel', () => { + cy.visit('/controlpanel/language'); + cy.get('main').within(() => { + cy.get('.field-wrapper-always_show_selector').should('not.exist'); + cy.get('.field-wrapper-display_flags').should('not.exist'); + }); + }); +}); diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index cc43b0a37e..eb3164e6dd 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -227,6 +227,9 @@ controlpanels The group can be one of the default groups 'General', 'Content', 'Security', 'Add-on Configuration', 'Users and Groups' or a custom group. +filterControlPanelsSchema + A schema factory for a control panel. It is used internally, to tweak the schemas provided by the controlpanel endpoint, to make them fit for Volto. + workflowMapping It's an object that defines the mapping between workflow states/transitions and the color that should show in the change Workflow dropdown. This is the default: diff --git a/src/components/manage/Controlpanels/Controlpanel.jsx b/src/components/manage/Controlpanels/Controlpanel.jsx index 3cdb6c3410..a6fc83740f 100644 --- a/src/components/manage/Controlpanels/Controlpanel.jsx +++ b/src/components/manage/Controlpanels/Controlpanel.jsx @@ -17,6 +17,8 @@ import { toast } from 'react-toastify'; import { Form, Icon, Toolbar, Toast } from '@plone/volto/components'; import { updateControlpanel, getControlpanel } from '@plone/volto/actions'; +import config from '@plone/volto/registry'; + import saveSVG from '@plone/volto/icons/save.svg'; import clearSVG from '@plone/volto/icons/clear.svg'; @@ -147,6 +149,8 @@ class Controlpanel extends Component { * @returns {string} Markup for the component. */ render() { + const { filterControlPanelsSchema } = config.settings; + if (this.props.controlpanel) { return (
@@ -155,7 +159,7 @@ class Controlpanel extends Component {
{ + const panelType = controlpanel['@id'].split('/').pop(); + + const unwantedSettings = { + language: ['display_flags', 'always_show_selector'], + search: ['enable_livesearch'], + site: [ + 'display_publication_date_in_byline', + 'icon_visibility', + 'thumb_visibility', + 'no_thumbs_portlet', + 'no_thumbs_lists', + 'no_thumbs_summary', + 'no_thumbs_tables', + 'thumb_scale_portlet', + 'thumb_scale_listing', + 'thumb_scale_table', + 'thumb_scale_summary', + 'toolbar_position', + 'toolbar_logo', + 'default_page', + ], + editing: ['available_editors', 'default_editor', 'ext_editor'], + imaging: [ + 'highpixeldensity_scales', + 'quality_2x', + 'quality_3x', + 'picture_variants', + 'image_captioning', + ], + }; + + // Creates modified version of properties object + const newPropertiesObj = Object.fromEntries( + Object.entries(controlpanel.schema.properties).filter( + ([key, val]) => !(unwantedSettings[panelType] || []).includes(key), + ), + ); + // Filters props.controlpanel.schema.fieldsets.fields to only valid/relevant fields + const filterFields = (fields) => { + return fields.filter( + (field) => !(unwantedSettings[panelType] || []).includes(field), + ); + }; + // Creates modified version of fieldsets array + const newFieldsets = controlpanel.schema.fieldsets.map((fieldset) => { + return { ...fieldset, fields: filterFields(fieldset.fields) }; + }); + + // Returns clone of props.controlpanel.schema, with updated properties/fieldsets + return { + ...controlpanel.schema, + properties: newPropertiesObj, + fieldsets: newFieldsets, + }; +}; diff --git a/src/config/index.js b/src/config/index.js index 7e852cf386..5cae5c90c6 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -24,7 +24,7 @@ import { loadables } from './Loadables'; import { workflowMapping } from './Workflows'; import { contentIcons } from './ContentIcons'; -import { controlPanelsIcons } from './ControlPanels'; +import { controlPanelsIcons, filterControlPanelsSchema } from './ControlPanels'; import { richtextEditorSettings, richtextViewSettings } from './RichTextEditor'; @@ -141,6 +141,7 @@ let config = { showTags: true, controlpanels: [], controlPanelsIcons, + filterControlPanelsSchema, externalRoutes: [ // URL to be considered as external // { diff --git a/test-setup-config.js b/test-setup-config.js index 9588a09e03..975db614cf 100644 --- a/test-setup-config.js +++ b/test-setup-config.js @@ -21,7 +21,10 @@ import { import FromHTMLCustomBlockFn from '@plone/volto/config/RichTextEditor/FromHTML'; import { contentIcons } from '@plone/volto/config/ContentIcons'; -import { controlPanelsIcons } from '@plone/volto/config/ControlPanels'; +import { + controlPanelsIcons, + filterControlPanelsSchema, +} from '@plone/volto/config/ControlPanels'; // we need to do a redefinition here because of circular import issues // because draftjs-based components are not really tested, this is basically @@ -65,6 +68,7 @@ config.set('settings', { ], }, controlPanelsIcons, + filterControlPanelsSchema, apiExpanders: [], downloadableObjects: ['File'], viewableInBrowserObjects: [], From 251eb90df2cc219363520438fab6fe50e4419a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Fri, 18 Nov 2022 14:59:16 +0100 Subject: [PATCH 032/326] Hide not relevant for Volto control panels from site setup, further refine not used inner settings for site control panel (#3915) --- CHANGELOG.md | 1 + .../manage/Controlpanels/Controlpanels.jsx | 113 ++++++++++-------- src/config/ControlPanels.js | 16 +++ src/config/index.js | 7 +- test-setup-config.js | 2 + 5 files changed, 85 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b15bb8ab1b..8b1915a036 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ### Bugfix - Hide control panel settings that are not relevant to Volto @danalvrz +- Hide not relevant for Volto control panels from site setup, further refine not used inner settings for site control panel @sneridagh - Fix ObjectWidget handling of `default` values coming from schemas. @tiberiuichim ### Internal diff --git a/src/components/manage/Controlpanels/Controlpanels.jsx b/src/components/manage/Controlpanels/Controlpanels.jsx index 30c3ff2196..ce248ee66e 100644 --- a/src/components/manage/Controlpanels/Controlpanels.jsx +++ b/src/components/manage/Controlpanels/Controlpanels.jsx @@ -173,60 +173,67 @@ class Controlpanels extends Component { return el; }) : []; + const { filterControlPanels } = config.settings; const controlpanels = map( - concat(this.props.controlpanels, customcontrolpanels, [ - { - '@id': '/addons', - group: this.props.intl.formatMessage(messages.general), - title: this.props.intl.formatMessage(messages.addons), - }, - { - '@id': '/database', - group: this.props.intl.formatMessage(messages.general), - title: this.props.intl.formatMessage(messages.database), - }, - { - '@id': '/rules', - group: this.props.intl.formatMessage(messages.content), - title: this.props.intl.formatMessage(messages.contentRules), - }, - { - '@id': '/undo', - group: this.props.intl.formatMessage(messages.general), - title: this.props.intl.formatMessage(messages.undo), - }, - { - '@id': '/aliases', - group: this.props.intl.formatMessage(messages.general), - title: this.props.intl.formatMessage(messages.urlmanagement), - }, - { - '@id': '/moderate-comments', - group: this.props.intl.formatMessage(messages.content), - title: this.props.intl.formatMessage(messages.moderatecomments), - }, - { - '@id': '/users', - group: this.props.intl.formatMessage( - messages.usersControlPanelCategory, - ), - title: this.props.intl.formatMessage(messages.users), - }, - { - '@id': '/usergroupmembership', - group: this.props.intl.formatMessage( - messages.usersControlPanelCategory, - ), - title: this.props.intl.formatMessage(messages.usergroupmemberbership), - }, - { - '@id': '/groups', - group: this.props.intl.formatMessage( - messages.usersControlPanelCategory, - ), - title: this.props.intl.formatMessage(messages.groups), - }, - ]), + concat( + filterControlPanels(this.props.controlpanels), + customcontrolpanels, + [ + { + '@id': '/addons', + group: this.props.intl.formatMessage(messages.general), + title: this.props.intl.formatMessage(messages.addons), + }, + { + '@id': '/database', + group: this.props.intl.formatMessage(messages.general), + title: this.props.intl.formatMessage(messages.database), + }, + { + '@id': '/rules', + group: this.props.intl.formatMessage(messages.content), + title: this.props.intl.formatMessage(messages.contentRules), + }, + { + '@id': '/undo', + group: this.props.intl.formatMessage(messages.general), + title: this.props.intl.formatMessage(messages.undo), + }, + { + '@id': '/aliases', + group: this.props.intl.formatMessage(messages.general), + title: this.props.intl.formatMessage(messages.urlmanagement), + }, + { + '@id': '/moderate-comments', + group: this.props.intl.formatMessage(messages.content), + title: this.props.intl.formatMessage(messages.moderatecomments), + }, + { + '@id': '/users', + group: this.props.intl.formatMessage( + messages.usersControlPanelCategory, + ), + title: this.props.intl.formatMessage(messages.users), + }, + { + '@id': '/usergroupmembership', + group: this.props.intl.formatMessage( + messages.usersControlPanelCategory, + ), + title: this.props.intl.formatMessage( + messages.usergroupmemberbership, + ), + }, + { + '@id': '/groups', + group: this.props.intl.formatMessage( + messages.usersControlPanelCategory, + ), + title: this.props.intl.formatMessage(messages.groups), + }, + ], + ), (controlpanel) => ({ ...controlpanel, id: last(controlpanel['@id'].split('/')), diff --git a/src/config/ControlPanels.js b/src/config/ControlPanels.js index 5720e1eb03..1dc3cc480a 100644 --- a/src/config/ControlPanels.js +++ b/src/config/ControlPanels.js @@ -42,6 +42,15 @@ export const controlPanelsIcons = { aliases: linkSVG, }; +export const filterControlPanels = (controlpanels) => { + const HIDDEN_CONTROL_PANELS = ['markup', 'content-rules']; + + return controlpanels.filter( + (controlpanel) => + !HIDDEN_CONTROL_PANELS.includes(controlpanel['@id'].split('/').pop()), + ); +}; + // Filters props.controlpanel.schema to only valid/relevant fields export const filterControlPanelsSchema = (controlpanel) => { const panelType = controlpanel['@id'].split('/').pop(); @@ -64,6 +73,13 @@ export const filterControlPanelsSchema = (controlpanel) => { 'toolbar_position', 'toolbar_logo', 'default_page', + 'site_logo', + 'site_favicon', + 'site_favicon_mimetype', + 'exposeDCMetaTags', + 'enable_sitemap', + 'robots_txt', + 'webstats_js', ], editing: ['available_editors', 'default_editor', 'ext_editor'], imaging: [ diff --git a/src/config/index.js b/src/config/index.js index 5cae5c90c6..67299bed1b 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -24,7 +24,11 @@ import { loadables } from './Loadables'; import { workflowMapping } from './Workflows'; import { contentIcons } from './ContentIcons'; -import { controlPanelsIcons, filterControlPanelsSchema } from './ControlPanels'; +import { + controlPanelsIcons, + filterControlPanels, + filterControlPanelsSchema, +} from './ControlPanels'; import { richtextEditorSettings, richtextViewSettings } from './RichTextEditor'; @@ -141,6 +145,7 @@ let config = { showTags: true, controlpanels: [], controlPanelsIcons, + filterControlPanels, filterControlPanelsSchema, externalRoutes: [ // URL to be considered as external diff --git a/test-setup-config.js b/test-setup-config.js index 975db614cf..350f0f90a3 100644 --- a/test-setup-config.js +++ b/test-setup-config.js @@ -23,6 +23,7 @@ import { contentIcons } from '@plone/volto/config/ContentIcons'; import { controlPanelsIcons, + filterControlPanels, filterControlPanelsSchema, } from '@plone/volto/config/ControlPanels'; @@ -68,6 +69,7 @@ config.set('settings', { ], }, controlPanelsIcons, + filterControlPanels, filterControlPanelsSchema, apiExpanders: [], downloadableObjects: ['File'], From 1b56afad9052a495c9a862a7118a6193ed22fc53 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 15:03:02 +0100 Subject: [PATCH 033/326] Release 16.0.0-alpha.51 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1915a036..8131f4dc5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 16.0.0 (unreleased) +## 16.0.0-alpha.51 (2022-11-18) ### Breaking diff --git a/package.json b/package.json index e218541575..26bc5a47de 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "16.0.0-alpha.50", + "version": "16.0.0-alpha.51", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 29cae91c278d74987b2d2d3e45e8a6f690fada69 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 15:03:09 +0100 Subject: [PATCH 034/326] Back to development --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8131f4dc5b..41238f8877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 16.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + +### Documentation + ## 16.0.0-alpha.51 (2022-11-18) ### Breaking From d40342cb76edf009feef251c13bc3c33f0025676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Fri, 18 Nov 2022 15:59:49 +0100 Subject: [PATCH 035/326] Revert "Configure Jest's moduleNameMapper with AddonConfigurationRegistry (#3913)" (#3919) This reverts commit 76bd39c411bb3fc9c975631bd36f1697779b2cde. --- .gitignore | 2 -- CHANGELOG.md | 1 - package.json | 1 + razzle.config.js | 21 ++++----------------- 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index cdbc798738..18ffb13b0d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,6 @@ lighthouse-report.html .vscode/ .#* *~ -/.settings/ -.*project # Python /api/.installed.cfg diff --git a/CHANGELOG.md b/CHANGELOG.md index 41238f8877..1cc576e111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,6 @@ ### Internal -- Configure Jest's `moduleNameMapper` with `AddonConfigurationRegistry`. Fix https://github.com/plone/volto/issues/3870 @wesleybl - Ignore `.tool-versions` file - Minor updates to dependencies - Update Cypress 11 @sneridagh diff --git a/package.json b/package.json index 26bc5a47de..66976454b8 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "moduleNameMapper": { "@plone/volto/package.json": "/package.json", "@plone/volto/(.*)$": "/src/$1", + "@plone/volto-slate": "/packages/volto-slate/src", "~/config": "/src/config", "~/../locales/${lang}.json": "/locales/en.json", "(.*)/locales/(.*)": "/locales/$2", diff --git a/razzle.config.js b/razzle.config.js index 0013fd8b27..02e3db88a9 100644 --- a/razzle.config.js +++ b/razzle.config.js @@ -21,12 +21,6 @@ const packageJson = require(path.join(projectRootPath, 'package.json')); const registry = new AddonConfigurationRegistry(projectRootPath); -const addonCustomizationPaths = registry.getAddonCustomizationPaths(); -const addonsFromEnvVarCustomizationPaths = registry.getAddonsFromEnvVarCustomizationPaths(); -const projectCustomizationPaths = registry.getProjectCustomizationPaths(); -const resolveAliases = registry.getResolveAliases(); - - const defaultModify = ({ env: { target, dev }, webpackConfig: config, @@ -209,14 +203,14 @@ const defaultModify = ({ ]; config.resolve.alias = { - ...addonCustomizationPaths, - ...addonsFromEnvVarCustomizationPaths, - ...projectCustomizationPaths, + ...registry.getAddonCustomizationPaths(), + ...registry.getAddonsFromEnvVarCustomizationPaths(), + ...registry.getProjectCustomizationPaths(), ...config.resolve.alias, '../../theme.config$': `${projectRootPath}/theme/theme.config`, 'volto-themes': `${registry.voltoPath}/theme/themes`, 'load-volto-addons': addonsLoaderPath, - ...resolveAliases, + ...registry.getResolveAliases(), '@plone/volto': `${registry.voltoPath}/src`, // to be able to reference path uncustomized by webpack '@plone/volto-original': `${registry.voltoPath}/src`, @@ -322,13 +316,6 @@ module.exports = { plugins, modifyJestConfig: ({ jestConfig }) => { jestConfig.testEnvironment = 'jsdom'; - jestConfig.moduleNameMapper = { - ...addonCustomizationPaths, - ...addonsFromEnvVarCustomizationPaths, - ...projectCustomizationPaths, - ...resolveAliases, - ...jestConfig.moduleNameMapper, - }; return jestConfig; }, modifyWebpackConfig: ({ From f0540d1cfad6a09965860407df531f90bd8d8605 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 16:00:25 +0100 Subject: [PATCH 036/326] Typo in GH workflow --- .github/workflows/acceptance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index bf63dd0a24..72ee3a5718 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -192,7 +192,7 @@ jobs: record: true parallel: false # Since they run on different node versions, we can't parallel browser: chrome - group: Core Basic - Plone 6 + group: Core Basic - Plone 5 spec: cypress/tests/core/basic/**/*.js start: | make start-test-acceptance-server-5 From 9d915c229e4a4f5880c8a2f959c98891a1599354 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 16:01:16 +0100 Subject: [PATCH 037/326] Prepare for release --- CHANGELOG.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cc576e111..39e2374c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,9 @@ ## 16.0.0 (unreleased) -### Breaking - -### Feature - ### Bugfix -### Internal - -### Documentation +- Revert "Configure Jest's moduleNameMapper with AddonConfigurationRegistry" (#3913) due to a regression in projects @sneridagh ## 16.0.0-alpha.51 (2022-11-18) From d1e670fde5402b9f3b0ebae35520de500aa448be Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 16:01:53 +0100 Subject: [PATCH 038/326] Release 16.0.0-alpha.52 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e2374c03..77ebafe875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 16.0.0 (unreleased) +## 16.0.0-alpha.52 (2022-11-18) ### Bugfix diff --git a/package.json b/package.json index 66976454b8..9291efac1d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "16.0.0-alpha.51", + "version": "16.0.0-alpha.52", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 23c994597dd2064292057329c6c45d10b8c025c1 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 16:01:59 +0100 Subject: [PATCH 039/326] Back to development --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ebafe875..bc2aa84322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 16.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + +### Documentation + ## 16.0.0-alpha.52 (2022-11-18) ### Bugfix From e21bd5bb635e4ae0c18c1a5fb6b7b378ed9e1001 Mon Sep 17 00:00:00 2001 From: David Glick Date: Fri, 18 Nov 2022 09:29:20 -0800 Subject: [PATCH 040/326] Proposal for a more intuitive add block button (#3815) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Proposal for a more intuitive add block button * add new blocks on click only for main BlocksForm * add setting for legacy add button * changelog and z-index fix * Remove unnecessary focusing of slate block to add other blocks in tests * move useEvent to helpers * Revert "Remove unnecessary focusing of slate block to add other blocks in tests" This reverts commit 98329a515f5b1ce74dac4e9973d84d6e7327bffb. * set legacyAddButton: true for now * rename setting to config.experimental.addBlockButton.enabled * Fix tests * Fix leftover value from testing * lint Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 2 + .../blocks/Text/DefaultTextBlockEditor.jsx | 32 +++++++------- .../manage/BlockChooser/BlockChooser.jsx | 7 +++- .../manage/BlockChooser/BlockChooser.test.jsx | 2 + .../BlockChooser/BlockChooserButton.jsx | 19 +++++---- .../BlockChooser/BlockChooserButton.test.jsx | 19 +-------- .../BlockChooserButton.test.jsx.snap | 2 - .../manage/Blocks/Block/BlocksForm.jsx | 18 +++++++- .../manage/Blocks/Block/BlocksForm.test.jsx | 2 + .../manage/Blocks/Block/EditBlockWrapper.jsx | 42 ++++++++++++++++++- src/components/manage/Blocks/Text/Edit.jsx | 3 +- src/components/theme/App/App.jsx | 24 +++++++++-- src/config/index.js | 6 +++ src/helpers/Blocks/Blocks.js | 6 +-- src/helpers/Utils/useEvent.js | 16 +++++++ src/helpers/index.js | 1 + src/registry.js | 8 ++++ theme/themes/pastanaga/extras/blocks.less | 38 ++++++++++++----- theme/themes/pastanaga/extras/main.less | 4 ++ 19 files changed, 187 insertions(+), 64 deletions(-) create mode 100644 src/helpers/Utils/useEvent.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bc2aa84322..63089d1fc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Feature +- There is an experimental setting to move the button for adding a new block to show below any selected block, instead of only on the left of empty text blocks. Set `config.experimental.addBlockButton.enabled = true` to enable it. @davisagli + ### Bugfix ### Internal diff --git a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx index d18d128302..694866e1b5 100644 --- a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +++ b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx @@ -247,21 +247,23 @@ export const DefaultTextBlockEditor = (props) => { }} - {selected && !data.plaintext?.trim() && !disableNewBlocks && ( - { - onSelectBlock(onInsertBlock(id, value)); - }} - onMutateBlock={onMutateBlock} - allowedBlocks={allowedBlocks} - blocksConfig={blocksConfig} - size="24px" - className="block-add-button" - properties={properties} - /> - )} + {!config.experimental.addBlockButton.enabled && + selected && + !data.plaintext?.trim() && + !disableNewBlocks && ( + { + onSelectBlock(onInsertBlock(id, value)); + }} + onMutateBlock={onMutateBlock} + allowedBlocks={allowedBlocks} + blocksConfig={blocksConfig} + size="24px" + properties={properties} + /> + )}
diff --git a/src/components/manage/BlockChooser/BlockChooser.jsx b/src/components/manage/BlockChooser/BlockChooser.jsx index 2b547a75b6..5c97683981 100644 --- a/src/components/manage/BlockChooser/BlockChooser.jsx +++ b/src/components/manage/BlockChooser/BlockChooser.jsx @@ -131,7 +131,12 @@ const BlockChooser = ({ }; return ( -
+
setFilterValue(value)} searchValue={filterValue} diff --git a/src/components/manage/BlockChooser/BlockChooser.test.jsx b/src/components/manage/BlockChooser/BlockChooser.test.jsx index 8773e21069..bf636a5158 100644 --- a/src/components/manage/BlockChooser/BlockChooser.test.jsx +++ b/src/components/manage/BlockChooser/BlockChooser.test.jsx @@ -8,6 +8,8 @@ import config from '@plone/volto/registry'; const blockSVG = {}; +config.experimental = { addBlockButton: { enabled: false } }; + config.blocks.blocksConfig = { title: { id: 'title', diff --git a/src/components/manage/BlockChooser/BlockChooserButton.jsx b/src/components/manage/BlockChooser/BlockChooserButton.jsx index 2922bbd013..3943c1ee5f 100644 --- a/src/components/manage/BlockChooser/BlockChooserButton.jsx +++ b/src/components/manage/BlockChooser/BlockChooserButton.jsx @@ -3,6 +3,7 @@ import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib'; import addSVG from '@plone/volto/icons/circle-plus.svg'; import { blockHasValue } from '@plone/volto/helpers'; import { Icon, BlockChooser } from '@plone/volto/components'; +import config from '@plone/volto/registry'; import { Button } from 'semantic-ui-react'; import { defineMessages, useIntl } from 'react-intl'; @@ -16,7 +17,9 @@ const messages = defineMessages({ export const ButtonComponent = (props) => { const intl = useIntl(); const { - className = 'block-add-button', + className = `block-add-button${ + config.experimental.addBlockButton.enabled ? ' new-add-block' : '' + }`, size = '19px', onShowBlockChooser, } = props; @@ -75,12 +78,14 @@ const BlockChooserButton = (props) => { return ( <> - {!disableNewBlocks && !blockHasValue(data) && ( - setAddNewBlockOpened(true)} - /> - )} + {!disableNewBlocks && + (config.experimental.addBlockButton.enabled || + !blockHasValue(data)) && ( + setAddNewBlockOpened(true)} + /> + )} {addNewBlockOpened && ( { - const store = mockStore({ - intl: { - locale: 'en', - messages: {}, - }, - }); - - const data = {}; // Volto plays safe with unknown data - const { container } = render( - - - , - ); - expect(container).toMatchSnapshot(); -}); - test('Renders a button', () => { const store = mockStore({ intl: { diff --git a/src/components/manage/BlockChooser/__snapshots__/BlockChooserButton.test.jsx.snap b/src/components/manage/BlockChooser/__snapshots__/BlockChooserButton.test.jsx.snap index 7c9035efcb..cb9d6b9b3c 100644 --- a/src/components/manage/BlockChooser/__snapshots__/BlockChooserButton.test.jsx.snap +++ b/src/components/manage/BlockChooser/__snapshots__/BlockChooserButton.test.jsx.snap @@ -10,8 +10,6 @@ exports[`Can render a custom button 1`] = `
`; -exports[`Does not render if the block is not empty 1`] = `
`; - exports[`Renders a button 1`] = `
)} + {config.experimental.addBlockButton.enabled && showBlockChooser && ( + { + if (blockHasValue(data)) { + onSelectBlock(onInsertBlock(id, value)); + } else { + onChangeBlock(id, value); + } + }} + onMutateBlock={onMutateBlock} + allowedBlocks={allowedBlocks} + blocksConfig={blocksConfig} + size="24px" + properties={properties} + /> + )}
diff --git a/src/components/manage/Blocks/Text/Edit.jsx b/src/components/manage/Blocks/Text/Edit.jsx index 4501c2fa2f..cf369d8eb8 100644 --- a/src/components/manage/Blocks/Text/Edit.jsx +++ b/src/components/manage/Blocks/Text/Edit.jsx @@ -329,7 +329,7 @@ export class EditComponent extends Component { }} /> - {this.props.selected && ( + {!config.experimental.addBlockButton.enabled && this.props.selected && ( )} diff --git a/src/components/theme/App/App.jsx b/src/components/theme/App/App.jsx index 787eb7739f..91fdb49b8b 100644 --- a/src/components/theme/App/App.jsx +++ b/src/components/theme/App/App.jsx @@ -3,7 +3,7 @@ * @module components/theme/App/App */ -import { Component } from 'react'; +import React, { Component } from 'react'; import jwtDecode from 'jwt-decode'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; @@ -67,6 +67,11 @@ class App extends Component { errorInfo: null, }; + constructor(props) { + super(props); + this.mainRef = React.createRef(); + } + /** * @method componentWillReceiveProps * @param {Object} nextProps Next properties @@ -92,6 +97,15 @@ class App extends Component { config.settings.errorHandlers.forEach((handler) => handler(error)); } + dispatchContentClick = (event) => { + if (event.target === event.currentTarget) { + const rect = this.mainRef.current.getBoundingClientRect(); + if (event.clientY > rect.bottom) { + document.dispatchEvent(new Event('voltoClickBelowContent')); + } + } + }; + /** * Render method. * @method render @@ -144,8 +158,12 @@ class App extends Component { pathname={this.props.pathname} contentLanguage={this.props.content?.language?.token} > - -
+ +
{this.props.connectionRefused ? ( diff --git a/src/config/index.js b/src/config/index.js index 67299bed1b..fe754dca0a 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -168,6 +168,11 @@ let config = { workflowMapping, errorHandlers: [], // callables for unhandled errors }, + experimental: { + addBlockButton: { + enabled: false, + }, + }, widgets: { ...widgetMapping, default: defaultWidget, @@ -193,6 +198,7 @@ let config = { }; ConfigRegistry.settings = config.settings; +ConfigRegistry.experimental = config.experimental; ConfigRegistry.blocks = config.blocks; ConfigRegistry.views = config.views; ConfigRegistry.widgets = config.widgets; diff --git a/src/helpers/Blocks/Blocks.js b/src/helpers/Blocks/Blocks.js index 3e9c3aa881..f38935f71c 100644 --- a/src/helpers/Blocks/Blocks.js +++ b/src/helpers/Blocks/Blocks.js @@ -233,7 +233,7 @@ export function mutateBlock(formData, id, value) { * @param {number} value New block's value * @return {Array} New block id, New form data */ -export function insertBlock(formData, id, value, current = {}) { +export function insertBlock(formData, id, value, current = {}, offset = 0) { const blocksFieldname = getBlocksFieldname(formData); const blocksLayoutFieldname = getBlocksLayoutFieldname(formData); const index = formData[blocksLayoutFieldname].items.indexOf(id); @@ -253,9 +253,9 @@ export function insertBlock(formData, id, value, current = {}) { }, [blocksLayoutFieldname]: { items: [ - ...formData[blocksLayoutFieldname].items.slice(0, index), + ...formData[blocksLayoutFieldname].items.slice(0, index + offset), newBlockId, - ...formData[blocksLayoutFieldname].items.slice(index), + ...formData[blocksLayoutFieldname].items.slice(index + offset), ], }, }, diff --git a/src/helpers/Utils/useEvent.js b/src/helpers/Utils/useEvent.js new file mode 100644 index 0000000000..8df4dc105c --- /dev/null +++ b/src/helpers/Utils/useEvent.js @@ -0,0 +1,16 @@ +import { useEffect } from 'react'; + +/** + * Hook to listen for an event on the document + */ +export function useEvent(event, handler) { + useEffect(() => { + // initiate the event handler + document.addEventListener(event, handler); + + // this will clean up the event every time the component is re-rendered + return function cleanup() { + document.removeEventListener(event, handler); + }; + }); +} diff --git a/src/helpers/index.js b/src/helpers/index.js index 67a824a8b0..7644f21041 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -100,6 +100,7 @@ export { asyncConnect } from './AsyncConnect'; export { userHasRoles } from './User/User'; // export { injectLazyLibs } from './Loadable/Loadable'; export { useDetectClickOutside } from './Utils/useDetectClickOutside'; +export { useEvent } from './Utils/useEvent'; export { usePrevious } from './Utils/usePrevious'; export { usePagination } from './Utils/usePagination'; export useUndoManager from './UndoManager/useUndoManager'; diff --git a/src/registry.js b/src/registry.js index 0916afac50..3b9674a468 100644 --- a/src/registry.js +++ b/src/registry.js @@ -26,6 +26,14 @@ class Config { this._data.settings = settings; } + get experimental() { + return this._data.experimental; + } + + set experimental(experimental) { + this._data.experimental = experimental; + } + get blocks() { return this._data.blocks; } diff --git a/theme/themes/pastanaga/extras/blocks.less b/theme/themes/pastanaga/extras/blocks.less index 8865d36010..b79f9afac8 100644 --- a/theme/themes/pastanaga/extras/blocks.less +++ b/theme/themes/pastanaga/extras/blocks.less @@ -23,7 +23,6 @@ .block .block:not(.inner)::before { position: absolute; z-index: -1; - z-index: -1; top: -9px; left: -9px; width: ~'calc(100% + 18px)'; @@ -72,7 +71,6 @@ .ui.drag.block.video, .ui.drag.block.image { - z-index: 2; // This fixes the floating images in Volto Editor! Muahahaha display: block; // This fixes two left floated items in a row @@ -470,18 +468,29 @@ body.has-toolbar.has-sidebar-collapsed .ui.wrapper > .ui.inner.block.full { } .ui.basic.button.block-add-button { - // .ui.basic.button.block-delete-button { position: absolute; z-index: 2; - // margin-top: -21px; - top: -2px; - padding: 0; - border: none; margin-bottom: 0; -webkit-box-shadow: none; box-shadow: none; color: #b8c6c8 !important; - transform: translateX(-40px); + + &.new-add-block { + bottom: -26px; + left: 50%; + padding: 0 !important; + background: white !important; + border: none !important; + border-radius: 50% !important; + transform: translateX(-50%); + } + + &:not(.new-add-block) { + top: -2px; + padding: 0; + border: none; + transform: translateX(-40px); + } &:hover, &:focus { @@ -705,14 +714,23 @@ body.has-toolbar.has-sidebar-collapsed .ui.wrapper > .ui.inner.block.full { .blocks-chooser { position: absolute; z-index: 10; - top: -12px; - left: -9px; + top: 100%; + left: 50%; width: 310px; padding: 4px; background-color: rgba(255, 255, 255, 0.975); border-radius: 2px; box-shadow: 0 0 8px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05); + &.new-add-block { + transform: translate(-50%, 22px); + } + + &:not(.new-add-block) { + top: -12px; + left: -9px; + } + .ui.basic.button { display: flex; width: 100px; diff --git a/theme/themes/pastanaga/extras/main.less b/theme/themes/pastanaga/extras/main.less index cb207680af..6aeb31658c 100644 --- a/theme/themes/pastanaga/extras/main.less +++ b/theme/themes/pastanaga/extras/main.less @@ -32,6 +32,10 @@ body { } } +body.cms-ui .ui.basic.segment.content-area { + cursor: text; +} + #main { display: flex; flex: 1; From e77a03b3108236759a8fd643edca0f59c9a5ff9e Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 18:31:46 +0100 Subject: [PATCH 041/326] Prepare for release --- CHANGELOG.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63089d1fc2..6c6eea59c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,10 @@ ## 16.0.0 (unreleased) -### Breaking - ### Feature - There is an experimental setting to move the button for adding a new block to show below any selected block, instead of only on the left of empty text blocks. Set `config.experimental.addBlockButton.enabled = true` to enable it. @davisagli -### Bugfix - -### Internal - -### Documentation - ## 16.0.0-alpha.52 (2022-11-18) ### Bugfix From cda596738c46a91c676fe095bc229518b24655f3 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 18:32:30 +0100 Subject: [PATCH 042/326] Release 16.0.0-alpha.53 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6eea59c6..df5ff7f7fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 16.0.0 (unreleased) +## 16.0.0-alpha.53 (2022-11-18) ### Feature diff --git a/package.json b/package.json index 9291efac1d..dd4e0bf70a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "16.0.0-alpha.52", + "version": "16.0.0-alpha.53", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 898ab6ed58245f17da9e4955c9e90f0f2df6f9b0 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 18:32:36 +0100 Subject: [PATCH 043/326] Back to development --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df5ff7f7fc..e7a34e293d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 16.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + +### Documentation + ## 16.0.0-alpha.53 (2022-11-18) ### Feature From b2c28daa97bf0dac660c413862ec8985ddc21ab6 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 18:34:39 +0100 Subject: [PATCH 044/326] Prepare for release --- CHANGELOG.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a34e293d..324902c5bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,9 @@ ## 16.0.0 (unreleased) -### Breaking - ### Feature -### Bugfix - -### Internal - -### Documentation +- Releasing RC1 @sneridagh ## 16.0.0-alpha.53 (2022-11-18) From c7b6c0b2d5aea2709352396e6adc1e1840f3083c Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 18:40:57 +0100 Subject: [PATCH 045/326] Release 16.0.0-rc.1 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 324902c5bd..3eeb19f23d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 16.0.0 (unreleased) +## 16.0.0-rc.1 (2022-11-18) ### Feature diff --git a/package.json b/package.json index dd4e0bf70a..abe3928fd0 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "16.0.0-alpha.53", + "version": "16.0.0-rc.1", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 57a975767e35de21ed5f91632a6e1745afb1a9c0 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 18 Nov 2022 18:41:03 +0100 Subject: [PATCH 046/326] Back to development --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eeb19f23d..8d72d551a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## 16.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + +### Documentation + ## 16.0.0-rc.1 (2022-11-18) ### Feature From 2a14b1d4423ee3106715f15994c4ed42a80a3dc0 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 19 Nov 2022 00:06:41 -0800 Subject: [PATCH 047/326] Remove sentryOptions from settings reference Clean up deploying/sentry.md See https://github.com/plone/volto/issues/3922 --- .../configuration/settings-reference.md | 35 ---- ...message.png => sentry-backend-message.png} | Bin ...essage.png => sentry-frontend-message.png} | Bin ...entry_messages.png => sentry-messages.png} | Bin docs/source/deploying/sentry.md | 191 ++++++++++++------ 5 files changed, 128 insertions(+), 98 deletions(-) rename docs/source/deploying/{sentry_backend_message.png => sentry-backend-message.png} (100%) rename docs/source/deploying/{sentry_frontend_message.png => sentry-frontend-message.png} (100%) rename docs/source/deploying/{sentry_messages.png => sentry-messages.png} (100%) diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index eb3164e6dd..21d62772dc 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -28,41 +28,6 @@ navDepth defaultBlockType The default block type in Volto is "text", which uses the current DraftJS-based implementation for the rich text editor. Future alternative rich text editors will need to use this setting and replace it with their block type. The block definition should also include the `blockHasValue` function, which is needed to activate the Block Chooser functionality. See this function signature in [Blocks > Settings](../blocks/settings.md). -sentryOptions - Sentry configuration: - - ```js - import { - settings as defaultSettings, - } from '@plone/volto/config'; - - const settings = { - ...defaultSettings, - sentryOptions: { - ...defaultSettings.sentryOptions, - dsn: 'https://key@sentry.io/1', - environment: 'production', - release: '1.2.3', - serverName: 'volto', - tags: { - site: 'foo.bar', - app: 'test_app', - logger: 'volto', - }, - extras: { - key: 'value', - }, - integrations: [ - ...defaultSettings.sentryOptions.integrations, - // new MyAwesomeIntegration() - ] - } - }; - ``` - ```{seealso} - See more about [Sentry integration](../deploying/sentry.md). - ``` - contentIcons With this property you can configure Content Types icons. Those are visible in Contents view (ex "Folder contents"). The default diff --git a/docs/source/deploying/sentry_backend_message.png b/docs/source/deploying/sentry-backend-message.png similarity index 100% rename from docs/source/deploying/sentry_backend_message.png rename to docs/source/deploying/sentry-backend-message.png diff --git a/docs/source/deploying/sentry_frontend_message.png b/docs/source/deploying/sentry-frontend-message.png similarity index 100% rename from docs/source/deploying/sentry_frontend_message.png rename to docs/source/deploying/sentry-frontend-message.png diff --git a/docs/source/deploying/sentry_messages.png b/docs/source/deploying/sentry-messages.png similarity index 100% rename from docs/source/deploying/sentry_messages.png rename to docs/source/deploying/sentry-messages.png diff --git a/docs/source/deploying/sentry.md b/docs/source/deploying/sentry.md index ea2da02699..e019d4e102 100644 --- a/docs/source/deploying/sentry.md +++ b/docs/source/deploying/sentry.md @@ -4,50 +4,92 @@ myst: "description": "Volto Integration with Sentry" "property=og:description": "Volto Integration with Sentry" "property=og:title": "Integration with Sentry" - "keywords": "Volto, Plone, frontend, React, Integration, Sentry" + "keywords": "Volto, Plone, frontend, React, Integration, Sentry, volto-sentry, add-on" --- # Integration with Sentry +Volto can be configured to work with [Sentry.io](https://sentry.io/welcome/). +Sentry is a monitoring platform that can help identify the cause of errors in your project. + + ## Prerequisities -1. In Sentry create a new organization, and add a project to it -2. On the projects settings page, from Client Keys (DSN), take the SENTRY_DSN -3. Create an API Token: on the top left corner, click on your name -> API keys and create a new token, "project:write" scope should be selected. -**Note:** Instructions tested with Sentry 9.1.2 +1. Install the add-on [`volto-sentry`](https://github.com/collective/volto-sentry). +1. In Sentry, create a new organization, and add a project to it. +1. On the projects settings page, from {guilabel}`Client Keys (DSN)`, take the `SENTRY_DSN`. +1. Create an API Token: in the top-left corner, click on {guilabel}`your name -> API keys`, and create a new token. + {guilabel}`project:write` scope should be selected. + +```{note} +Instructions tested with Sentry 9.1.2. +``` + +```{versionchanged} 16.0.0.alpha.45 +Sentry was removed from Volto core and into a separate add-on [`volto-sentry`](https://github.com/collective/volto-sentry) in 16.0.0.alpha.45. +``` + ## Setup -Volto creates bundles of the source codes, if an error is sent to sentry, it will only show the traceback in the bundles. To have nice traceback, we have to upload the source code and source map in sentry. -This doesn't have to be done manually, we can configure our volto application to do all the steps automatically. -There are 2 ways to configure the application: +Volto creates bundles of the source codes. +If an error is sent to Sentry, it will only show the traceback in the bundles. +To have a nice traceback, we have to upload the source code and source map to Sentry. +This can be configured in our Volto application to do all the steps automatically. + +There are 2 ways to configure the application. + + +### 1. Build time + +This method can be used when the application is deployed directly on a host machine and built locally. -### 1. Buildtime -This can be used when the application is deployed directly on a host machine, and built locally. +The configuration is done using environment variables. -The configuration is done using environment variables: - - SENTRY_DSN - required to enable the feature - - SENTRY_URL - the url of sentry - - SENTRY_AUTH_TOKEN - the authentication token for sentry - - SENTRY_ORG - the name of the organization in sentry - - SENTRY_PROJECT -the name of the project in sentry - - SENTRY_RELEASE - release number - - SENTRY_FRONTEND_CONFIG - optional, here we can specify TAGS - and ADDITIONAL DATA for the messages from the browser we send to sentry - - SENTRY_BACKEND_CONFIG - same as SENTRY_FRONTEND_CONFIG, but we configure the messages from the backend +`SENTRY_DSN` +: Required to enable the feature. -If these env variables are configured, when the app is built, a new release will be created in sentry, and the source code and source maps will be uploaded it. -After starting the application if an error will occure, the errors will be sent to sentry, and will be linked to the specified release. +`SENTRY_URL` +: The URL of Sentry. -Example of usage: +`SENTRY_AUTH_TOKEN` +: The authentication token for Sentry. -```bash -SENTRY_URL=https://mysentry.com SENTRY_AUTH_TOKEN=foo SENTRY_ORG=my_organization SENTRY_PROJECT=new_project SENTRY_RELEASE=2.0.0 SENTRY_DSN=https://boo@sentry.com/1 yarn build +`SENTRY_ORG` +: The name of the organization in Sentry. + +`SENTRY_PROJECT` +: The name of the project in Sentry. + +`SENTRY_RELEASE` +: The release number. + +`SENTRY_FRONTEND_CONFIG` +: Optional, here we can specify `TAGS` and `ADDITIONAL DATA` for the messages we send to Sentry from the browser. + +`SENTRY_BACKEND_CONFIG` +: Same as `SENTRY_FRONTEND_CONFIG`, but we configure the messages from the backend. + +If these environment variables are configured when the app is built, a new release will be created in Sentry, and the source code and source maps will be uploaded to it. +After starting the application, if an error occurs, the errors will be sent to Sentry, and will be linked to the specified release. + +Example usage: + +```shell +SENTRY_URL=https://mysentry.com \ +SENTRY_AUTH_TOKEN=foo \ +SENTRY_ORG=my_organization \ +SENTRY_PROJECT=new_project \ +SENTRY_RELEASE=2.0.0 \ +SENTRY_DSN=https://boo@sentry.com/1 yarn build node build/server.js ``` + + ### 2. Runtime -Within your Volto project or a dedicated Volto add-on you can configure Sentry via `settings.sentryOptions` configuration key: + +Within your Volto project or a dedicated Volto add-on, you can configure Sentry via the `settings.sentryOptions` configuration key: ```js import { @@ -76,31 +118,51 @@ const settings = { ] } }; -See more about [Sentry Custom Integrations](https://docs.sentry.io/platforms/javascript/configuration/integrations) - -In case you plan to use the application using docker, you will not want to have the sentry setup in the docker image. -The configuration for setting up sentry on runtime is very similar as how we set it up for buildtime, but with some small differences: - - - SENTRY_URL - the url of sentry - - SENTRY_AUTH_TOKEN - the authentication token for sentry - - SENTRY_ORG - the name of the organization in sentry - - SENTRY_PROJECT -the name of the project in sentry - - SENTRY_RELEASE - release number - - RAZZLE_SENTRY_DSN - required to enable the feature - - RAZZLE_SENTRY_FRONTEND_CONFIG - optional, here we can specify TAGS - and ADDITIONAL DATA for the messages from the browser we send to sentry - - RAZZLE_SENTRY_BACKEND_CONFIG - same as RAZZLE_SENTRY_FRONTEND_CONFIG, but we configure the messages from the backend - - RAZZLE_SENTRY_RELEASE - release number, should be the same as SENTRY_RELEASE - -In the entrypoint of our docker image we have to add the ./create-sentry-release.sh script. When the container is started, this script will check in sentry if the specified release already exists, if not, it will create it and upload the source code and the source maps. -The script can be executed also manually and if we want to overwrite the existing files in sentry, we can use the --force flag: - -```bash +``` + +See more about [Sentry Custom Integrations](https://docs.sentry.io/platforms/javascript/configuration/integrations/custom/). + +In case you plan to use the application using Docker, you will not want to have the Sentry setup in the Docker image. +The configuration for setting up Sentry on runtime is very similar to how we set it up for build time, but with some small differences. + +`SENTRY_URL` +: The URL of Sentry. + +`SENTRY_AUTH_TOKEN` +: The authentication token for Sentry. + +`SENTRY_ORG` +: The name of the organization in Sentry. + +`SENTRY_PROJECT` +: The name of the project in Sentry. + +`SENTRY_RELEASE` +: The release number. + +`RAZZLE_SENTRY_DSN` +: Required to enable the feature. + +`RAZZLE_SENTRY_FRONTEND_CONFIG` +: Optional, here we can specify `TAGS` and `ADDITIONAL DATA` for the messages we send to Sentry from the browser. + +`RAZZLE_SENTRY_BACKEND_CONFIG` +: Same as `RAZZLE_SENTRY_FRONTEND_CONFIG`, but we configure the messages from the backend. + +`RAZZLE_SENTRY_RELEASE` + The release number, which should be the same as `SENTRY_RELEASE`. + +In the entrypoint of our Docker image, we have to add the script `./create-sentry-release.sh`. +When the container is started, this script will check in Sentry if the specified release already exists, and if not, it will create it and upload the source code and the source maps. +The script can also be executed manually, and if we want to overwrite the existing files in Sentry, we can use the `--force` flag. + +```shell ./create-sentry-release.sh --force ``` -Example of entrypoint: -```bash +Example of entrypoint. + +```shell #!/usr/bin/env bash set -Ex @@ -129,7 +191,7 @@ echo "Starting Volto" exec "$@" ``` -Starting with docker: +Starting the container with Docker. ```shell docker run -p 3000:3000 -p 3001:3001 \ @@ -162,16 +224,19 @@ services: - RAZZLE_SENTRY_RELEASE=2.0.0 ``` + ## Configuration options -This applies to both SENTRY_FRONTEND_CONFIG and SENTRY_BACKEND_CONFIG +This applies to both `SENTRY_FRONTEND_CONFIG` and `SENTRY_BACKEND_CONFIG`. + +If you are using _build time_ configuration, you have to use `SENTRY_FRONTEND_CONFIG` and `SENTRY_BACKEND_CONFIG`. -**Note:** In case you are using buildtime configuration you have to use SENTRY_FRONTEND_CONFIG and SENTRY_BACKEND_CONFIG. +If you are using _runtime_ configuration, use `RAZZLE_SENTRY_FRONTEND_CONFIG` and `RAZZLE_SENTRY_BACKEND_CONFIG`. -But if you are using runtime configuration, use RAZZLE_SENTRY_FRONTEND_CONFIG and RAZZLE_SENTRY_BACKEND_CONFIG +We have the possibility to add `TAGS` and `ADDITIONAL DATA` for our messages for categorization in Sentry. +We can configure these two variables separately, as we might want to separate the messages from frontend and backend. -We have the possibility to add TAGS and ADDITIONAL DATA for our messages for categorization in SENTRY. We can configure these 2 variables separately, as we might want to separate the messages from frontend and backend. -Example of configurations: +Example of configuration. ```json { @@ -190,7 +255,7 @@ Example of configurations: } ``` -Example of usage with buildtime setup: +Example of usage with build time setup. ```console SENTRY_URL=https://mysentry.com @@ -204,7 +269,7 @@ SENTRY_BACKEND_CONFIG='{"tags":{"site":"www.test.com","app":"test_app"} yarn bui node build/server.js ``` -Example with Docker Compose: +Example with Docker Compose. ```yaml version: '3' @@ -226,11 +291,11 @@ services: - RAZZLE_SENTRY_BACKEND_CONFIG={"tags":{"site":"www.test.com","app":"test_app"},"extras":{"logger":"javascript-backend", "server":"server#1"}} ``` -## Example of messages in SENTRY +## Example messages in Sentry -1. List of messages -![](sentry_messages.png) -2. Messages from the frontend, with its own TAGS and ADDITIONAL DATA -![](sentry_frontend_message.png) -3. Messages from the backend, with its own TAGS and ADDITIONAL DATA -![](sentry_backend_message.png) +1. List of messages + ![](sentry-messages.png) +2. Messages from the frontend, with its own `TAGS` and `ADDITIONAL DATA` + ![](sentry-frontend-message.png) +3. Messages from the backend, with its own `TAGS` and `ADDITIONAL DATA` + ![](sentry-backend-message.png) From eae3c128821f24c52e0aa20b3dedf8d27676451a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 19 Nov 2022 00:07:42 -0800 Subject: [PATCH 048/326] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d72d551a0..c3afce0865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ ### Documentation +- Remove `sentryOptions` from settings reference. Clean up `deploying/sentry.md`. @stevepiercy + + ## 16.0.0-rc.1 (2022-11-18) ### Feature From 47db22c3e4ae01f201e43b2d570984f33c7e55d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 20 Nov 2022 10:10:43 +0100 Subject: [PATCH 049/326] Fix initial block settings data defaults, improve `applySchemaDefaults` and `ObjectWidget` schema support (#3925) (#3928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Init data with block defaults * Add start:coresandbox command; add some fields with default values * WIP on cypress test * Rest of test * Add cy.wait * Roll back changes to Field, no longer pass explicitely defaults; this is the job on the code that instantiates a new form, it needs to applySchemaDefaults * Use onChangeFormData in InlineForm * Pass down onChangeFormData from BlockDataForm * Only pass down onChangeFormData if we have onChangeBlock * Fix test block * Fix defaults for ObjectListWidget * Don't pass onChangeFormData * Cleanup tests * Remove comment * Add ObjectWidget case * Add cy.getContent; finish test for objectwidget / objectlistwidget defaults * Code formatting * Add changelog * No need for async here * One more test for the applySchemaDefaults helper * Improve docs, add warning in upgrade guide * Add console.warn notice in BlockDataForm if no onChangeBlock is passed down. * More tests with different types of fields/widgets * More resilient Cypress test * BBB with applySchemaDefaults without intl passed down * Fix initial block settings data defaults, improve `applySchemaDefaults` and `ObjectWidget` schema support (#3925) * Init data with block defaults * Add start:coresandbox command; add some fields with default values * WIP on cypress test * Rest of test * Add cy.wait * Roll back changes to Field, no longer pass explicitely defaults; this is the job on the code that instantiates a new form, it needs to applySchemaDefaults * Use onChangeFormData in InlineForm * Pass down onChangeFormData from BlockDataForm * Only pass down onChangeFormData if we have onChangeBlock * Fix test block * Fix defaults for ObjectListWidget * Don't pass onChangeFormData * Cleanup tests * Remove comment * Add ObjectWidget case * Add cy.getContent; finish test for objectwidget / objectlistwidget defaults * Code formatting * Add changelog * No need for async here * One more test for the applySchemaDefaults helper * Improve docs, add warning in upgrade guide * Add console.warn notice in BlockDataForm if no onChangeBlock is passed down. * More tests with different types of fields/widgets * More resilient Cypress test * BBB with applySchemaDefaults without intl passed down Co-authored-by: Victor Fernandez de Alba * Fix initial block settings data defaults, improve `applySchemaDefaults` and `ObjectWidget` schema support (#3925) * Init data with block defaults * Add start:coresandbox command; add some fields with default values * WIP on cypress test * Rest of test * Add cy.wait * Roll back changes to Field, no longer pass explicitely defaults; this is the job on the code that instantiates a new form, it needs to applySchemaDefaults * Use onChangeFormData in InlineForm * Pass down onChangeFormData from BlockDataForm * Only pass down onChangeFormData if we have onChangeBlock * Fix test block * Fix defaults for ObjectListWidget * Don't pass onChangeFormData * Cleanup tests * Remove comment * Add ObjectWidget case * Add cy.getContent; finish test for objectwidget / objectlistwidget defaults * Code formatting * Add changelog * No need for async here * One more test for the applySchemaDefaults helper * Improve docs, add warning in upgrade guide * Add console.warn notice in BlockDataForm if no onChangeBlock is passed down. * More tests with different types of fields/widgets * More resilient Cypress test * BBB with applySchemaDefaults without intl passed down Co-authored-by: Victor Fernandez de Alba * Generator is aware of RC and beta versions when canary option is sele… (#3923) * Generator is aware of RC and beta versions when canary option is selected * Fix tests * Add filled in data test * Fix edge case when data is passed with values Co-authored-by: Tiberiu Ichim Co-authored-by: Tiberiu Ichim --- CHANGELOG.md | 2 + Makefile | 4 + cypress/support/commands.js | 24 ++ cypress/tests/coresandbox/fields.js | 63 +++++ docs/source/blocks/editcomponent.md | 6 +- docs/source/upgrade-guide/index.md | 32 ++- package.json | 1 + .../src/components/Blocks/TestBlock/Data.jsx | 1 + .../src/components/Blocks/TestBlock/Edit.jsx | 9 +- .../src/components/Blocks/TestBlock/schema.js | 44 +++- packages/generator-volto/CHANGELOG.md | 2 + packages/generator-volto/__tests__/app.js | 6 +- .../generator-volto/generators/app/utils.js | 6 +- .../blocks/Text/DefaultTextBlockEditor.jsx | 1 + .../src/elementEditor/PluginEditor.jsx | 1 + .../manage/Blocks/Block/BlocksForm.jsx | 26 +- src/components/manage/Form/BlockDataForm.jsx | 26 +- .../manage/Form/BlockDataForm.test.jsx | 27 +- src/components/manage/Form/Field.jsx | 12 +- src/components/manage/Form/InlineForm.jsx | 36 ++- .../manage/Form/InlineForm.test.jsx | 13 +- .../manage/Widgets/ObjectListWidget.jsx | 42 ++-- src/helpers/Blocks/Blocks.js | 57 ++++- src/helpers/Blocks/Blocks.test.js | 234 ++++++++++++++++++ 24 files changed, 598 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3afce0865..d90d0cbcd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugfix +- Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim + ### Internal ### Documentation diff --git a/Makefile b/Makefile index 60c3df4b5b..254e3818e5 100644 --- a/Makefile +++ b/Makefile @@ -268,6 +268,10 @@ start-test-acceptance-server-coresandbox test-acceptance-server-coresandbox: ## start-test-acceptance-frontend-coresandbox: ## Start the CoreSandbox Acceptance Frontend Fixture ADDONS=coresandbox RAZZLE_API_PATH=http://localhost:55001/plone yarn build && yarn start:prod +.PHONY: start-test-acceptance-frontend-coresandbox-dev +start-test-acceptance-frontend-coresandbox-dev: ## Start the CoreSandbox Acceptance Frontend Fixture in dev mode + ADDONS=coresandbox RAZZLE_API_PATH=http://localhost:55001/plone yarn start + .PHONY: test-acceptance-coresandbox test-acceptance-coresandbox: ## Start CoreSandbox Cypress Acceptance Tests NODE_ENV=production CYPRESS_API=plone $(NODEBIN)/cypress open --config specPattern='cypress/tests/coresandbox/**/*.{js,jsx,ts,tsx}' diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 5739a60e38..426661cf6f 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -195,6 +195,30 @@ Cypress.Commands.add('removeContent', ({ path = '' }) => { }); }); +// Get content +Cypress.Commands.add('getContent', ({ path = '' }) => { + let api_url, auth; + if (Cypress.env('API') === 'guillotina') { + api_url = GUILLOTINA_API_URL; + auth = { + user: 'root', + pass: 'root', + }; + } else { + api_url = PLONE_API_URL; + auth = ploneAuthObj; + } + + return cy.request({ + method: 'get', + url: `${api_url}/${path}`, + headers: { + Accept: 'application/json', + }, + auth: auth, + }); +}); + // --- Add DX Content-Type ---------------------------------------------------------- Cypress.Commands.add('addContentType', (name) => { let api_url, auth; diff --git a/cypress/tests/coresandbox/fields.js b/cypress/tests/coresandbox/fields.js index 7770b54596..007603e14e 100644 --- a/cypress/tests/coresandbox/fields.js +++ b/cypress/tests/coresandbox/fields.js @@ -1,4 +1,67 @@ context('Special fields Acceptance Tests', () => { + describe.only('Form with default values', () => { + beforeEach(() => { + // given a logged in editor and a page in edit mode + cy.visit('/'); + cy.autologin(); + cy.createContent({ + contentType: 'Document', + contentId: 'document', + contentTitle: 'Test document', + }); + cy.visit('/document'); + cy.waitForResourceToLoad('@navigation'); + cy.waitForResourceToLoad('@breadcrumbs'); + cy.waitForResourceToLoad('@actions'); + cy.waitForResourceToLoad('@types'); + cy.waitForResourceToLoad('document'); + cy.navigate('/document/edit'); + cy.getSlateTitle(); + }); + + it('As an editor I can add a block that has default values', () => { + cy.intercept('PATCH', '/**/document').as('save'); + cy.intercept('GET', '/**/@types/Document').as('schema'); + cy.getSlate().click(); + cy.get('.button .block-add-button').click({ force: true }); + cy.wait(100); + cy.get('.blocks-chooser .mostUsed .button.testBlock').click(); + + cy.findByLabelText('Field with default').click(); + cy.get('#field-firstWithDefault').should( + 'have.value', + 'Some default value', + ); + + cy.findByLabelText('Add item').click(); + cy.findAllByText('Item #1').should('have.length', 1); + + cy.findByLabelText('Extra').should('have.value', 'Extra default'); + cy.get('#toolbar-save').click(); + cy.wait('@save'); + + cy.navigate('/document/edit'); + cy.wait('@schema'); + + cy.findAllByText('Test Block Edit').click(); + + cy.get('#field-firstWithDefault').should( + 'have.value', + 'Some default value', + ); + cy.findByLabelText('Extra').should('have.value', 'Extra default'); + + cy.getContent({ path: '/document' }).should((response) => { + const { body } = response; + const [, testBlock] = Object.entries(body.blocks).find( + ([, block]) => block['@type'] === 'testBlock', + ); + expect(testBlock.style).to.deep.equal({ color: 'red' }); + expect(testBlock.slides[0].extraDefault).to.deep.equal('Extra default'); + }); + }); + }); + describe('ObjectListWidget', () => { beforeEach(() => { // given a logged in editor and a page in edit mode diff --git a/docs/source/blocks/editcomponent.md b/docs/source/blocks/editcomponent.md index 1c65e77beb..daae5580b7 100644 --- a/docs/source/blocks/editcomponent.md +++ b/docs/source/blocks/editcomponent.md @@ -104,11 +104,11 @@ To render this form and make it available to the edit component: ```jsx import schema from './schema'; -import InlineForm from '@plone/volto/components/manage/Form/InlineForm'; +import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm'; import { Icon } from '@plone/volto/components'; - } schema={schema} title={schema.title} @@ -120,7 +120,9 @@ import { Icon } from '@plone/volto/components'; [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={this.props.data} + block={block} /> ; ``` diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 2cf382bf87..fcb503c62b 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -521,6 +521,34 @@ You'll have to add a script in your `package.json` file called `prepare`: After execute it, `husky` will install itself in the `.husky` folder of your project. Then you need to create the default hook scripts in `.husky` that you want to execute. You can copy over the Volto ones (take a look in Volto's `.husky` folder). +### Better defaults handling + +````{versionadded} 16.0.0-alpha.51 +Prior to this version we had a default values handling in schemas for blocks settings that was faulty and buggy. The state inferred was not deterministic and depending on the fields with defaults present. + +In order to correct this and allow Volto to handle defaults in a correct way we have to pass down an additional prop to the `BlockDataForm` like this: + + ```{code-block} jsx + :linenos: + :emphasize-lines: 10 + { + this.props.onChangeBlock(this.props.block, { + ...this.props.data, + [id]: value, + }); + }} + onChangeBlock={onChangeBlock} + formData={this.props.data} + block={block} + /> + ``` + +whenever we use it in our custom or add-ons code. +```` + (volto-upgrade-guide-15.x.x)= ## Upgrading to Volto 15.x.x @@ -2076,7 +2104,9 @@ The blocks engine now takes care of the keyboard navigation of the blocks, so yo The focus management is also transferred to the engine, so it's not needed for your block to manage the focus. However, if your block does indeed require to manage its own focus, then you should mark it with the `blockHasOwnFocusManagement` property in the blocks configuration object: -``` js hl_lines="10" +```{code-block} jsx +:linenos: +:emphasize-lines: 10 text: { id: 'text', title: 'Text', diff --git a/package.json b/package.json index abe3928fd0..f4fd64196c 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "postinstall": "make patches", "analyze": "BUNDLE_ANALYZE=true razzle build", "start": "razzle start", + "start:coresandbox": "ADDONS=coresandbox razzle start", "build": "razzle build --noninteractive", "prepare": "husky install", "test": "razzle test --maxWorkers=50%", diff --git a/packages/coresandbox/src/components/Blocks/TestBlock/Data.jsx b/packages/coresandbox/src/components/Blocks/TestBlock/Data.jsx index 222902c309..ed934a6fd3 100644 --- a/packages/coresandbox/src/components/Blocks/TestBlock/Data.jsx +++ b/packages/coresandbox/src/components/Blocks/TestBlock/Data.jsx @@ -18,6 +18,7 @@ const TestBlockData = (props) => { [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={data} /> ); diff --git a/packages/coresandbox/src/components/Blocks/TestBlock/Edit.jsx b/packages/coresandbox/src/components/Blocks/TestBlock/Edit.jsx index bd700e0f52..96de54e3cc 100644 --- a/packages/coresandbox/src/components/Blocks/TestBlock/Edit.jsx +++ b/packages/coresandbox/src/components/Blocks/TestBlock/Edit.jsx @@ -3,18 +3,13 @@ import { SidebarPortal } from '@plone/volto/components'; import Data from './Data'; const TestBlockEdit = (props) => { - const { data, onChangeBlock, block, selected } = props; + const { selected } = props; return ( <>
Test Block Edit
- + ); diff --git a/packages/coresandbox/src/components/Blocks/TestBlock/schema.js b/packages/coresandbox/src/components/Blocks/TestBlock/schema.js index 3b28d827d8..3f059f8153 100644 --- a/packages/coresandbox/src/components/Blocks/TestBlock/schema.js +++ b/packages/coresandbox/src/components/Blocks/TestBlock/schema.js @@ -45,7 +45,13 @@ const itemSchema = (props) => { { id: 'default', title: 'Default', - fields: ['href', 'title', 'description', 'preview_image'], + fields: [ + 'href', + 'title', + 'description', + 'preview_image', + 'extraDefault', + ], }, ], @@ -74,6 +80,10 @@ const itemSchema = (props) => { mode: 'image', allowExternals: true, }, + extraDefault: { + title: 'Extra', + default: 'Extra default', + }, }, required: [], }; @@ -85,7 +95,13 @@ export const SliderSchema = (props) => ({ { id: 'default', title: 'Default', - fields: ['slides', 'fieldAfterObjectList', 'href'], + fields: [ + 'slides', + 'fieldAfterObjectList', + 'href', + 'firstWithDefault', + 'style', + ], }, ], properties: { @@ -109,6 +125,30 @@ export const SliderSchema = (props) => ({ ], allowExternals: true, }, + firstWithDefault: { + title: 'Field with default', + default: 'Some default value', + }, + style: { + widget: 'object', + schema: { + title: 'Style', + fieldsets: [ + { + id: 'default', + fields: ['color'], + title: 'Default', + }, + ], + properties: { + color: { + title: 'Color', + default: 'red', + }, + }, + required: [], + }, + }, }, required: [], }); diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 27425baf85..566f949740 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugfix +- Generator is aware of `rc`, `beta` and `alphas` as possible releases for canary @sneridagh + ### Internal ## 6.0.0-alpha.3 (2022-11-16) diff --git a/packages/generator-volto/__tests__/app.js b/packages/generator-volto/__tests__/app.js index cb6925c32b..de33dc9b6b 100644 --- a/packages/generator-volto/__tests__/app.js +++ b/packages/generator-volto/__tests__/app.js @@ -64,6 +64,10 @@ describe('generator-create-volto-app:app with canary option', () => { fs.readFileSync(path.join(tmpDir, 'test-volto/package.json'), 'utf8'), ); - expect(packageJSON.dependencies['@plone/volto']).toContain('alpha'); + expect( + packageJSON.dependencies['@plone/volto'].includes(['rc']) || + packageJSON.dependencies['@plone/volto'].includes(['beta']) || + packageJSON.dependencies['@plone/volto'].includes(['alpha']), + ).toBe(true); }); }); diff --git a/packages/generator-volto/generators/app/utils.js b/packages/generator-volto/generators/app/utils.js index 7771a81411..fa007014e3 100644 --- a/packages/generator-volto/generators/app/utils.js +++ b/packages/generator-volto/generators/app/utils.js @@ -69,7 +69,11 @@ async function getLatestCanaryVoltoVersion() { }); resp.on('end', () => { const res = JSON.parse(data.join('')); - resolve(res['dist-tags'].alpha); + resolve( + res['dist-tags'].rc || + res['dist-tags'].beta || + res['dist-tags'].alpha, + ); }); }, ) diff --git a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx index 694866e1b5..bdd3f42926 100644 --- a/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx +++ b/packages/volto-slate/src/blocks/Text/DefaultTextBlockEditor.jsx @@ -279,6 +279,7 @@ export const DefaultTextBlockEditor = (props) => { block={block} schema={schema} title={schema.title} + onChangeBlock={onChangeBlock} onChangeField={(id, value) => { onChangeBlock(block, { ...data, diff --git a/packages/volto-slate/src/elementEditor/PluginEditor.jsx b/packages/volto-slate/src/elementEditor/PluginEditor.jsx index 30410a1194..b7f866e7b7 100644 --- a/packages/volto-slate/src/elementEditor/PluginEditor.jsx +++ b/packages/volto-slate/src/elementEditor/PluginEditor.jsx @@ -82,6 +82,7 @@ const PluginEditor = (props) => { } return onChangeValues(id, value, formData, setFormData); }} + onChangeFormData={setFormData} formData={formData} headerActions={ <> diff --git a/src/components/manage/Blocks/Block/BlocksForm.jsx b/src/components/manage/Blocks/Block/BlocksForm.jsx index 77be6b78f7..c78681bc41 100644 --- a/src/components/manage/Blocks/Block/BlocksForm.jsx +++ b/src/components/manage/Blocks/Block/BlocksForm.jsx @@ -1,7 +1,12 @@ import React from 'react'; +import { useIntl } from 'react-intl'; import EditBlock from './Edit'; import { DragDropList } from '@plone/volto/components'; -import { getBlocks } from '@plone/volto/helpers'; +import { + getBlocks, + getBlocksFieldname, + applyBlockDefaults, +} from '@plone/volto/helpers'; import { addBlock, insertBlock, @@ -42,6 +47,7 @@ const BlocksForm = (props) => { const blockList = getBlocks(properties); const dispatch = useDispatch(); + const intl = useIntl(); const ClickOutsideListener = () => { onSelectBlock(null); @@ -117,6 +123,16 @@ const BlocksForm = (props) => { current, config.experimental.addBlockButton.enabled ? 1 : 0, ); + + const blocksFieldname = getBlocksFieldname(newFormData); + const blockData = newFormData[blocksFieldname][newId]; + newFormData[blocksFieldname][newId] = applyBlockDefaults({ + data: blockData, + intl, + metadata, + properties, + }); + onChangeFormData(newFormData); return newId; }; @@ -124,6 +140,14 @@ const BlocksForm = (props) => { const onAddBlock = (type, index) => { if (editable) { const [id, newFormData] = addBlock(properties, type, index); + const blocksFieldname = getBlocksFieldname(newFormData); + const blockData = newFormData[blocksFieldname][id]; + newFormData[blocksFieldname][id] = applyBlockDefaults({ + data: blockData, + intl, + metadata, + properties, + }); onChangeFormData(newFormData); return id; } diff --git a/src/components/manage/Form/BlockDataForm.jsx b/src/components/manage/Form/BlockDataForm.jsx index a710fe9fee..40dc64931a 100644 --- a/src/components/manage/Form/BlockDataForm.jsx +++ b/src/components/manage/Form/BlockDataForm.jsx @@ -1,6 +1,28 @@ +import React from 'react'; import { InlineForm } from '@plone/volto/components'; import { withVariationSchemaEnhancer } from '@plone/volto/helpers'; -const BlockDataForm = withVariationSchemaEnhancer(InlineForm); +const EnhancedBlockDataForm = withVariationSchemaEnhancer(InlineForm); -export default BlockDataForm; +export default function BlockDataForm(props) { + const { onChangeBlock, block } = props; + + if (!onChangeBlock) { + // eslint-disable-next-line no-console + console.warn( + 'BlockDataForm component is used without passing down onChangeBlock as props', + ); + } + + const onChangeFormData = React.useCallback( + (data) => onChangeBlock(block, data), + [block, onChangeBlock], + ); + + return ( + + ); +} diff --git a/src/components/manage/Form/BlockDataForm.test.jsx b/src/components/manage/Form/BlockDataForm.test.jsx index b72ae0b7a7..24b6e5c27c 100644 --- a/src/components/manage/Form/BlockDataForm.test.jsx +++ b/src/components/manage/Form/BlockDataForm.test.jsx @@ -8,6 +8,24 @@ import { Provider } from 'react-intl-redux'; const mockStore = configureStore(); +const withStateManagement = (Component) => ({ ...props }) => { + const [formData, onChangeFormData] = React.useState(props.formData || {}); + const onChangeField = (id, value) => { + onChangeFormData({ ...formData, [id]: value }); + }; + + // NOTE: onChangeBlock here is not "really" implemented + + return ( + onChangeFormData(data)} + formData={formData} + /> + ); +}; + beforeAll(() => { config.widgets = { id: {}, @@ -55,6 +73,7 @@ beforeAll(() => { describe('BlockDataForm', () => { it('should does not add variations to schema when unneeded', () => { + const WrappedBlockDataForm = withStateManagement(BlockDataForm); const store = mockStore({ intl: { locale: 'en', @@ -71,7 +90,7 @@ describe('BlockDataForm', () => { }; const { container } = render( - {}} @@ -85,6 +104,7 @@ describe('BlockDataForm', () => { }); it('should does not add variations when only one variation', () => { + const WrappedBlockDataForm = withStateManagement(BlockDataForm); const store = mockStore({ intl: { locale: 'en', @@ -101,7 +121,7 @@ describe('BlockDataForm', () => { }; const { container } = render( - {}} @@ -115,6 +135,7 @@ describe('BlockDataForm', () => { }); it('should add variations to schema', () => { + const WrappedBlockDataForm = withStateManagement(BlockDataForm); const store = mockStore({ intl: { locale: 'en', @@ -131,7 +152,7 @@ describe('BlockDataForm', () => { }; const { container } = render( - {}} diff --git a/src/components/manage/Form/Field.jsx b/src/components/manage/Form/Field.jsx index 7e8cf9df8d..f509903237 100644 --- a/src/components/manage/Form/Field.jsx +++ b/src/components/manage/Form/Field.jsx @@ -232,17 +232,9 @@ const DndConnectedField = injectLazyLibs(['reactDnd'])(UnconnectedField); const Field = (props) => props.onOrder ? ( - + ) : ( - + ); /** diff --git a/src/components/manage/Form/InlineForm.jsx b/src/components/manage/Form/InlineForm.jsx index d7e460a17b..57cee8e74b 100644 --- a/src/components/manage/Form/InlineForm.jsx +++ b/src/components/manage/Form/InlineForm.jsx @@ -6,6 +6,7 @@ import AnimateHeight from 'react-animate-height'; import { keys, map, isEqual } from 'lodash'; import { Field, Icon } from '@plone/volto/components'; +import { applySchemaDefaults } from '@plone/volto/helpers'; import upSVG from '@plone/volto/icons/up-key.svg'; import downSVG from '@plone/volto/icons/down-key.svg'; @@ -32,6 +33,7 @@ const InlineForm = (props) => { error, // Such as {message: "It's not good"} errors = {}, formData, + onChangeFormData, onChangeField, schema, title, @@ -49,26 +51,22 @@ const InlineForm = (props) => { // Will set field values from schema, by matching the default values const objectSchema = typeof schema === 'function' ? schema(props) : schema; - const initialData = { - ...Object.keys(objectSchema.properties).reduce( - (accumulator, currentField) => { - return objectSchema.properties[currentField].default - ? { - ...accumulator, - [currentField]: objectSchema.properties[currentField].default, - } - : accumulator; - }, - {}, - ), - ...formData, - }; - - Object.keys(initialData).forEach((k) => { - if (!isEqual(initialData[k], formData?.[k])) { - onChangeField(k, initialData[k]); - } + + const initialData = applySchemaDefaults({ + data: formData, + schema: objectSchema, + intl, }); + + if (onChangeFormData) { + onChangeFormData(initialData); + } else { + Object.keys(initialData).forEach((k) => { + if (!isEqual(initialData[k], formData?.[k])) { + onChangeField(k, initialData[k]); + } + }); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/components/manage/Form/InlineForm.test.jsx b/src/components/manage/Form/InlineForm.test.jsx index 76de8a6ad1..52067dd405 100644 --- a/src/components/manage/Form/InlineForm.test.jsx +++ b/src/components/manage/Form/InlineForm.test.jsx @@ -20,13 +20,18 @@ function NewBaseWidget(name) { } const withStateManagement = (Component) => ({ ...props }) => { - const [formData, setFormData] = React.useState(props.formData || {}); + const [formData, onChangeFormData] = React.useState(props.formData || {}); const onChangeField = (id, value) => { - setFormData({ ...formData, [id]: value }); + onChangeFormData({ ...formData, [id]: value }); }; return ( - + ); }; @@ -114,6 +119,7 @@ describe('Form', () => { expect(container).toMatchSnapshot(); }); + it('renders a form component with defaults in the schema - Checkboxes', () => { const store = mockStore({ intl: { @@ -163,7 +169,6 @@ describe('Form', () => { /> , ); - expect(container).toMatchSnapshot(); }); it('renders a form component with defaults in the schema - Number field', () => { diff --git a/src/components/manage/Widgets/ObjectListWidget.jsx b/src/components/manage/Widgets/ObjectListWidget.jsx index 12a6250c7a..b700c90cfd 100644 --- a/src/components/manage/Widgets/ObjectListWidget.jsx +++ b/src/components/manage/Widgets/ObjectListWidget.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { Accordion, Button, Segment } from 'semantic-ui-react'; import { DragDropList, FormFieldWrapper, Icon } from '@plone/volto/components'; +import { applySchemaDefaults } from '@plone/volto/helpers'; import ObjectWidget from '@plone/volto/components/manage/Widgets/ObjectWidget'; import upSVG from '@plone/volto/icons/up-key.svg'; @@ -70,14 +71,14 @@ const ObjectListWidget = (props) => { onChange, schemaExtender, } = props; - const [activeColumn, setActiveColumn] = React.useState(value.length - 1); + const [activeObject, setActiveObject] = React.useState(value.length - 1); const intl = useIntl(); - function handleChangeColumn(e, blockProps) { + function handleChangeActiveObject(e, blockProps) { const { index } = blockProps; - const newIndex = activeColumn === index ? -1 : index; + const newIndex = activeObject === index ? -1 : index; - setActiveColumn(newIndex); + setActiveObject(newIndex); } const objectSchema = typeof schema === 'function' ? schema(props) : schema; @@ -98,18 +99,25 @@ const ObjectListWidget = (props) => { } onClick={(e) => { e.preventDefault(); - onChange(id, [ - ...value, - { - '@id': uuid(), - }, - ]); - setActiveColumn(value.length); + const data = { + '@id': uuid(), + }; + const objSchema = schemaExtender + ? schemaExtender(schema, data, intl) + : objectSchema; + const dataWithDefaults = applySchemaDefaults({ + data, + schema: objSchema, + intl, + }); + + onChange(id, [...value, dataWithDefaults]); + setActiveObject(value.length); }} >   - {/* Custom addMessage in schema, else default to english */} + {/* Custom addMessage in schema, else default to English */} {objectSchema.addMessage || `${intl.formatMessage(messages.add)} ${objectSchema.title}`} @@ -158,11 +166,11 @@ const ObjectListWidget = (props) => { > { > - {activeColumn === index ? ( + {activeObject === index ? ( ) : ( )}
- + { +export function applySchemaDefaults({ data = {}, schema, intl }) { + if (!intl && !_logged) { + // Old code that doesn't pass intl doesn't get ObjectWidget defaults + // eslint-disable-next-line no-console + console.warn( + `You should pass intl to any applySchemaDefaults call. By failing to pass + the intl object, your ObjectWidget fields will not get default values + extracted from their schema.`, + ); + _logged = true; + } + + const derivedData = merge( + Object.keys(schema.properties).reduce((accumulator, currentField) => { return typeof schema.properties[currentField].default !== 'undefined' ? { ...accumulator, [currentField]: schema.properties[currentField].default, } + : intl && + schema.properties[currentField].schema && + !(schema.properties[currentField].widget === 'object_list') // TODO: this should be renamed as itemSchema + ? { + ...accumulator, + [currentField]: { + ...applySchemaDefaults({ + data: { ...data[currentField], ...accumulator[currentField] }, + schema: + typeof schema.properties[currentField].schema === 'function' + ? schema.properties[currentField].schema({ + data: accumulator[currentField], + formData: accumulator[currentField], + intl, + }) + : schema.properties[currentField].schema, + intl, + }), + }, + } : accumulator; }, {}), - ...data, - }; + data, + ); + return derivedData; } @@ -404,7 +447,7 @@ export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) { : blockSchema; schema = applySchemaEnhancer({ schema, formData: data, intl }); - return applySchemaDefaults({ data, schema }); + return applySchemaDefaults({ data, schema, intl }); } export const buildStyleClassNamesFromData = (styles) => { diff --git a/src/helpers/Blocks/Blocks.test.js b/src/helpers/Blocks/Blocks.test.js index 31acee4cfb..a7f2281b30 100644 --- a/src/helpers/Blocks/Blocks.test.js +++ b/src/helpers/Blocks/Blocks.test.js @@ -165,6 +165,151 @@ config.blocks.blocksConfig.enhancedBlockCase2 = { }), }; +const itemSchema = (props) => { + return { + title: 'Item', + addMessage: 'Add', + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [ + 'href', + 'title', + 'description', + 'preview_image', + 'extraDefault', + ], + }, + ], + + properties: { + href: { + title: 'Source', + widget: 'object_browser', + mode: 'link', + selectedItemAttrs: [ + 'Title', + 'Description', + 'hasPreviewImage', + 'headtitle', + ], + allowExternals: true, + }, + title: { + title: 'title', + }, + description: { + title: 'description', + }, + preview_image: { + title: 'Image Override', + widget: 'object_browser', + mode: 'image', + allowExternals: true, + }, + extraDefault: { + title: 'Extra', + default: 'Extra default', + }, + }, + required: [], + }; +}; + +config.blocks.blocksConfig.slider = { + id: 'slider', + title: 'Slider', + group: 'Special', + restricted: false, + mostUsed: false, + blockHasOwnFocusManagement: true, + blockSchema: (props) => ({ + title: 'slider', + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: [ + 'slides', + 'fieldAfterObjectList', + 'href', + 'firstWithDefault', + 'style', + 'anotherWithDefault', + 'yetAnotherWithDefault', + ], + }, + ], + properties: { + slides: { + widget: 'object_list', + schema: itemSchema, + default: [ + { + '@id': 'asdasdasd-qweqwe-zxczxc', + extraDefault: + 'Extra default (Manual in parent slider widget default)', + }, + ], + }, + fieldAfterObjectList: { + title: 'Field after OL', + }, + href: { + widget: 'object_browser', + mode: 'link', + selectedItemAttrs: [ + 'Title', + 'Description', + 'hasPreviewImage', + 'headtitle', + ], + allowExternals: true, + }, + firstWithDefault: { + title: 'Field with default', + default: 'Some default value', + }, + style: { + widget: 'object', + schema: { + title: 'Style', + fieldsets: [ + { + id: 'default', + fields: ['color', 'theme'], + title: 'Default', + }, + ], + properties: { + color: { + title: 'Color', + default: 'red', + }, + theme: { + title: 'Theme', + default: 'primary', + }, + }, + required: [], + }, + }, + anotherWithDefault: { + title: 'Field with default 2', + default: 2, + type: 'number', + }, + yetAnotherWithDefault: { + title: 'Field with default 3', + default: ['one', 'two'], + type: 'array', + }, + }, + required: [], + }), +}; + config.settings.defaultBlockType = 'text'; describe('Blocks', () => { @@ -508,6 +653,95 @@ describe('Blocks', () => { description: 'already filled', }); }); + it('Sets data according to schema default values, top level and styling wrapper object field', () => { + const data = { + '@type': 'slider', + }; + const schema = config.blocks.blocksConfig.slider.blockSchema({ data }); + + // if you don't pass down intl, the ObjectWidget defaults are not applied + expect(applySchemaDefaults({ schema, data })).toEqual({ + '@type': 'slider', + anotherWithDefault: 2, + slides: [ + { + '@id': 'asdasdasd-qweqwe-zxczxc', + extraDefault: + 'Extra default (Manual in parent slider widget default)', + }, + ], + firstWithDefault: 'Some default value', + yetAnotherWithDefault: ['one', 'two'], + }); + + expect(applySchemaDefaults({ schema, data, intl: {} })).toEqual({ + '@type': 'slider', + anotherWithDefault: 2, + slides: [ + { + '@id': 'asdasdasd-qweqwe-zxczxc', + extraDefault: + 'Extra default (Manual in parent slider widget default)', + }, + ], + firstWithDefault: 'Some default value', + style: { + color: 'red', + theme: 'primary', + }, + yetAnotherWithDefault: ['one', 'two'], + }); + }); + + it('Sets data according to schema default values, keeps existing data', () => { + const schema = { + properties: { + style: { + widget: 'object', + schema: { + title: 'Style', + fieldsets: [ + { + id: 'default', + fields: ['color', 'theme'], + title: 'Default', + }, + ], + properties: { + color: { + title: 'Color', + default: 'red', + }, + theme: { + title: 'Theme', + default: 'primary', + }, + }, + required: [], + }, + }, + }, + }; + + expect( + applySchemaDefaults({ + schema, + data: { + '@type': 'slider', + style: { + theme: 'secondary', + }, + }, + intl: {}, + }), + ).toEqual({ + '@type': 'slider', + style: { + color: 'red', + theme: 'secondary', + }, + }); + }); }); describe('applyBlockDefaults', () => { From 2300c72db31715bedd725636ff05459f01b950cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 20 Nov 2022 10:11:17 +0100 Subject: [PATCH 050/326] =?UTF-8?q?Generator=20is=20aware=20of=20RC=20and?= =?UTF-8?q?=20beta=20versions=20when=20canary=20option=20is=20sele?= =?UTF-8?q?=E2=80=A6=20(#3923)=20(#3929)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Generator is aware of RC and beta versions when canary option is selected * Fix tests From bcd2dd613c74c286ad8074e35f47877043a67746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 20 Nov 2022 10:32:13 +0100 Subject: [PATCH 051/326] Tidy up upgrade guide for v16.x.x (#3930) (#3932) * Tidy up upgrade guide for v16.x.x * CHANGELOG note Co-authored-by: Steve Piercy --- CHANGELOG.md | 1 + docs/source/upgrade-guide/index.md | 300 ++++++++++++++++------------- 2 files changed, 163 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d90d0cbcd3..49f4ba8628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Documentation - Remove `sentryOptions` from settings reference. Clean up `deploying/sentry.md`. @stevepiercy +- Tidy up `upgrade-guide/index.md`. @stevepiercy ## 16.0.0-rc.1 (2022-11-18) diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index fcb503c62b..5cd7ec632c 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -11,19 +11,16 @@ myst: # Upgrade Guide -This upgrade guide lists all breaking changes in Volto and explains the - steps that are necessary to upgrade to the latest version. +This upgrade guide lists all breaking changes in Volto and explains the steps that are necessary to upgrade to the latest version. Volto uses Semantic Versioning. For more information see {ref}`volto-versioning-policy`. ```{note} -There are times when updating the Volto boilerplate (the one generated by -`@plone/generator-volto`) is enough to fulfill all the changes. If you haven't -heavily modified it, moving things around and copying over your dependencies might -do when dealing with upgrades. We keep the generator up to date and in sync with -current Volto release. Please notice that the generator is able to tell you when it -runs if it's outdated. The generator is also able to "update" your project with -the latest changes, and propose to you to merge the changes, so you can run it on top of your project by answering the prompt. +There are times when updating the Volto boilerplate (the one generated by `@plone/generator-volto`) is enough to fulfill all the changes. +If you have not heavily modified it, moving things around and copying over your dependencies might do when dealing with upgrades. +We keep the generator up to date and in sync with current Volto release. +Please notice that the generator is able to tell you when it runs if it's outdated. +The generator is also able to "update" your project with the latest changes, and propose to you to merge the changes, so you can run it on top of your project by answering the prompts. ``` @@ -31,7 +28,7 @@ the latest changes, and propose to you to merge the changes, so you can run it o ## Upgrading to Volto 16.x.x -### `volto-slate` is now core +### `volto-slate` is now in core From Volto 16.0.0-alpha.15 onwards, `volto-slate` is integrated into Volto core and enabled as the default text block. The previous text block `text` based on `draftJS` is now deprecated and is restricted (hidden) in Volto bootstrap. @@ -129,46 +126,53 @@ Version 16 is recommended. ### Upgraded to Razzle 4 ```{versionadded} 16.0.0-alpha.38 -Volto has upgraded from Razzle 3 to Razzle 4. You should perform these steps in case you are upgrading to this version or above. +Volto has upgraded from Razzle 3 to Razzle 4. +You should perform these steps in case you are upgrading to this version or above. ``` #### Steps after upgrade A few updates may be needed in existing projects: -1. Add the `cache` directory to `.gitignore`. -2. If `package.json` includes scripts that run `razzle test` with the `--env=jest-environment-jsdom-sixteen` option, update them to use `--env=jsdom` instead. -3. Update the `jest` configuration in `package.json` to replace the `jest-css-modules` transform: +1. Add the `cache` directory to `.gitignore`. +2. If `package.json` includes scripts that run `razzle test` with the `--env=jest-environment-jsdom-sixteen` option, update them to use `--env=jsdom` instead. +3. Update the `jest` configuration in `package.json` to replace the `jest-css-modules` transform: -```diff -"jest": { - "transform": { -- "^.+\\.css$": "jest-css-modules", -- "^.+\\.scss$": "jest-css-modules" - }, - "moduleNameMapper": { -+ "\\.(css|less|scss|sass)$": "identity-obj-proxy" - } -} -``` + ```diff + "jest": { + "transform": { + - "^.+\\.css$": "jest-css-modules", + - "^.+\\.scss$": "jest-css-modules" + }, + "moduleNameMapper": { + + "\\.(css|less|scss|sass)$": "identity-obj-proxy" + } + } + ``` -4. Add `--noninteractive` option to the build script +4. Add the option `--noninteractive` to the build script. -```diff -- "build": "razzle build", -+ "build": "razzle build --noninteractive", -``` + ```diff + - "build": "razzle build", + + "build": "razzle build --noninteractive", + ``` -5. If you use custom Razzle plugins, update them to use the new format with multiple functions: https://razzlejs.org/docs/upgrade-guide#plugins (the old format still works, but is deprecated). -6. If you have customized webpack loader configuration related to CSS, make sure it is updated to be compatible with PostCSS 8. -7. It's recommended that you remove your existing `node_modules` and start clean. -8. If the add-ons you are using are not yet updated to latest `@plone/scripts`, it's also recommended that you force the version in your build, setting this in `package.json`: +5. If you use custom Razzle plugins, update them to use the new format with multiple functions. + The old format still works, but is deprecated. -```json - "resolutions": { - "**/@plone/scripts": "^2.0.0" - } -``` + ```{seealso} + See https://razzlejs.org/docs/upgrade-guide#plugins + ``` + +6. If you have customized webpack loader configuration related to CSS, make sure it is updated to be compatible with PostCSS 8. +7. It's recommended that you remove your existing `node_modules` and start clean. +8. If the add-ons you are using are not yet updated to latest `@plone/scripts`, it's also recommended that you force the version in your build, setting this in `package.json`: + + ```json + "resolutions": { + "**/@plone/scripts": "^2.0.0" + } + ``` #### Upgrade and update add-ons dependency on `@plone/scripts` @@ -177,7 +181,7 @@ This applies only to Volto add-ons. ``` Most probably you are using `@plone/scripts` in your add-on, since it's used in i18n messageid generation and has other add-on utilities. -When upgrading to Volto 16.0.0-alpha.38 or above, you should upgrade `@plone/scripts` to a version 2.0.0 or above. +When upgrading to Volto 16.0.0-alpha.38 or above, you should upgrade `@plone/scripts` to version 2.0.0 or above. It's also recommended you move it from `dependencies` to `devDependencies`. ```diff @@ -216,15 +220,15 @@ index 2f4e1e8..51bd52b 100644 ### Jest is downgraded from version 27 to 26 -Razzle 4 internal API is only compatible with up to Jest 26. +Razzle 4 internal API is only compatible with Jest up to version 26. ### Upgrade to use yarn 3 Volto was using the old, classic yarn (v1). -It has become quite obsolete and yarn has evolved a lot during the last years. -We are updating Volto to be able to use it, however some changes have to be made in your projects configuration: +It has become quite obsolete and yarn has evolved a lot in recent years. +We updated Volto to be able to use it, however some changes have to be made in your project's configuration: -1. Enable yarn 3 in your projects, adding `.yarnrc.yml`: +1. Enable yarn 3 in your project, adding `.yarnrc.yml`: ```yml defaultSemverRangePrefix: "" @@ -246,11 +250,11 @@ We are updating Volto to be able to use it, however some changes have to be made npm i -g corepack ``` - ```{note} - It is highly recommended that you use latest Node 16. + ```{important} + It is highly recommended that you use the latest Node 16. ``` -2. Change your root project `Makefile` to include these commands: +2. Change your root project `Makefile` to include these commands: ```diff +.PHONY: install @@ -280,7 +284,7 @@ We are updating Volto to be able to use it, however some changes have to be made You can copy the file over to your project if you have not made any amendment to it. -3. Change your `package.json` scripts section: +3. Change your `package.json` scripts section: ```diff "version": "1.0.0", @@ -298,7 +302,7 @@ We are updating Volto to be able to use it, however some changes have to be made It does not support the `preinstall` lifecycle state, so we cannot trigger things while we run `yarn install` or just `yarn`. As a result, we moved all the commands related to the `preinstall` actions into `Makefile`, then updated the calls in both files as shown above. - ```{important} + ````{important} After making the changes in this step, we will have to modify our development workflow, especially if we use actions that happened during the `preinstall` state. The most relevant one is updating the code that is managed by `mrs-developer`. If you follow the code above, you'll need to call these `Makefile` commands before each `yarn install`. @@ -309,8 +313,10 @@ We are updating Volto to be able to use it, however some changes have to be made ``` Remember to update your CI scripts accordingly. + ```` -4. It doesn't allow to use commands not declared as direct dependencies, so in your projects you should add `razzle` and `@plone/scripts` as development dependencies: +4. Yarn 3 does not allow the use of commands not declared as direct dependencies. + In your project, you should add `razzle` and `@plone/scripts` as development dependencies: ```diff devDependencies: { @@ -318,7 +324,7 @@ We are updating Volto to be able to use it, however some changes have to be made + "razzle": "4.2.17", ``` -5. Replace your project `yarn.lock` for the Volto's one, then run `yarn` again. +5. Replace your project `yarn.lock` with Volto's, then run `yarn` again. ### Removed `date-fns` from build @@ -327,60 +333,68 @@ It was in the build because `Cypress` depended on it. After `Cypress` was upgraded, it no longer depends on `date-fns`. If your project still depends on `date-fns`, add it as a dependency of your project. -```{warning} +````{warning} The `date-fns` version present in the build was quite old (1.x.x series). Beware when using an updated version (2.x.x), as it may contain some breaking changes. -Also take a look at: https://dockyard.com/blog/2020/02/14/you-probably-don-t-need-moment-js-anymore + + +```{seealso} +https://dockyard.com/blog/2020/02/14/you-probably-don-t-need-moment-js-anymore +``` If you need to format dates in Volto, it's recommended to use the `FormattedDate` component in Volto core. It uses modern recommendations for date formatting on the web. -``` +```` ### Upgraded core to use Cypress 10 -Cypress has overhauled the testing app and included native support of Apple Silicon Computers from 10.2.0 onwards. -This improves dramatically the launch and test times in these machines. +Cypress has overhauled the testing app and included native support of Apple Silicon Computers beginning with version 10.2.0. +This dramatically improves the launch and test times in these machines. It also includes the new "component" testing feature that might be appealing in the near future. -The only drawback is that they also overhauled the configuration forcing to migrate from old config based on JSON files to a better JS-based one. They also changed and renamed some of the options. -Luckily, they provide a good reporting when old configuration is in place and an interactive migration wizard. +The only drawback is that they also overhauled the configuration, forcing migration from old configuration based on JSON files to a better JavaScript-based one. +They also changed and renamed some options. +Luckily, Cypress provides good reporting when old configuration is in place, and an interactive migration wizard. -Core configuration has been updated, but you will require to update your Cypress configuration if you want to use core's Cypress 10. -Could be that forcing your project to use older versions might still work with old configurations. +Core configuration has been updated, but you will need to update your Cypress configuration if you want to use core's Cypress 10. +It is possible that forcing your project to use older versions might still work with old configurations. +```{seealso} See https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-version-10-0 for more information. +``` ```{note} -Later on, the core has been upgraded to Cypress 11, however no changes to be made if you already upgraded to Cypress 10. +Later on, the core has been upgraded to Cypress 11. +However no changes need to be made if you already upgraded to Cypress 10. ``` ### The complete configuration registry is passed to the add-ons and the project configuration pipeline -The core in versions prior Volto 16.0.0-alpha.22 was passing a simplified version of the configuration registry (in fact, a plain object primitive) to the add-on list and project configuration pipeline. +The core in versions prior to Volto 16.0.0-alpha.22 passed a simplified version of the configuration registry—in fact, a plain object primitive—to the add-on list and project configuration pipeline. From Volto 16.0.0-alpha.22 onwards, the full configuration registry is passed through the pipeline. -This allows to have access to the advanced registry features, like the new component registry. -You may want to update your configuration in case that you were updating the primitive using basic object operators (spread, etc) in the config object itself. -For example, this won't work anymore: +This allows you to have access to the advanced registry features, like the new component registry. +You may want to update your configuration in case you updated the primitive using basic object operators (spread, and others) in the configuration object itself. +For example, this will not work anymore: ```js - return { - ...config, - blocks: { - ...config.blocks, - blocksConfig: { - ...config.blocks.blocksConfig, - ...addonBlocks, - listing: listing(config), - }, +return { + ...config, + blocks: { + ...config.blocks, + blocksConfig: { + ...config.blocks.blocksConfig, + ...addonBlocks, + listing: listing(config), }, - views: { - ...config.views, - contentTypesViews: { - ...config.views.contentTypesViews, - Folder: NewsAndEvents, - }, + }, + views: { + ...config.views, + contentTypesViews: { + ...config.views.contentTypesViews, + Folder: NewsAndEvents, }, - }; + }, +}; ``` Using the spread operator while you mutate the configuration object is not required. @@ -400,7 +414,7 @@ The other rules apply, so make sure you return the `config` object after mutatin ### Refactor the component registry API in the configuration registry -After a period of testing, this experimental feature has been refactored to adequate better to existing requirements. +After a period of testing, this experimental feature has been refactored to adequate existing requirements. #### Renamed `registry.resolve` to `registry.getComponent` @@ -411,7 +425,7 @@ After a period of testing, this experimental feature has been refactored to adeq #### `registry.getComponent` signature changes -It maintains signature compatibility with `registry.resolve` but introduces new arguments for bigger flexibility. +It maintains signature compatibility with `registry.resolve`, but introduces new arguments for greater flexibility. See documentation for more information. @@ -427,22 +441,22 @@ config.registerComponent({ ``` ```` -### Main workflow change menu changed from Pastanaga UI simplification to classic Plone implementation +### Main workflow change menu changed from Pastanaga UI simplification to Classic UI Plone implementation Pastanaga UI envisioned a simplification of the classic Plone workflow change dropdown. -The idea is that for users, the transition names were too cryptic and it was difficult to infer the destination state from them. -So the simplification was meant to show the destination state as a transition name, simplifying the user experience. +The transition names were too cryptic, and it was difficult to infer the destination state from them. +This simplification was meant to show the destination state as a transition name, simplifying the user experience. -This vision was partially implemented in Volto, bypassing the information coming from Plone, waiting for the next step: introduce this vision in Plone core (thus, change the workflow definitions) including this simplified mode, but maintaining the complete mode, with the full transition names. +This vision was partially implemented in Volto, bypassing the information coming from Plone, waiting for the next step: introduce this vision in Plone core (thus, change the workflow definitions) including this simplified mode, but maintaining the complete mode with the full transition names. -Since this never happened, we are going back to the classic mode, so the dropdown will show the transition names. +Since this never happened, we are going back to the Classic UI mode, so the dropdown will show the transition names. When the simplified vision is implemented, we will revisit it. #### Move Layout constants to `config.views.layoutViewsNamesMapping`. -The `constants` layout module was removed in favor of an object in the Configuration Registry: `config.views.layoutViewsNamesMapping`. +The `constants` layout module was removed in favor of an object in the configuration registry, `config.views.layoutViewsNamesMapping`. -If you have added or modified the Plone layout views literal mapping, you should now use this setting, and you can remove the module shadowing customization. +If you added or modified the Plone layout views literal mapping, you should now use this setting, and you can remove the module shadowing customization. You can now add an i18n `id` for any layout that you create as well, since the `Display` component is now i18n aware. @@ -482,11 +496,11 @@ defineMessages({ }) ``` -### `react-window` no longer a Volto dependency +### `react-window` is no longer a Volto dependency Volto used this library to generate dynamic "windowed/virtualized" select widget options. -It moved to use `react-virtualized` instead of `react-window` because it provides a more broad set of features that Volto required. -If you were using it in your project, you'll have to include it as a direct dependency of it from now on. +It moved to use `react-virtualized` instead of `react-window` because it provides a broader feature set that Volto requires. +If you were using it in your project, you will need to include it as a direct dependency from now on. ### Change the way the style wrapper is enabled and how to add the `styles` field @@ -495,23 +509,28 @@ We decided it is best to deal with it as any other schema field and enhance it v This improves the developer experience, especially when dealing with variations that can provide their own styles and other schema fields. ```{deprecated} 16.0.0-alpha.46 -The options `enableStyling` and `stylesSchema` no longer work. You need to provide them using your own block schema. If you are extending an existing one, you should add it as a normal `schemaEnhancer` modification. +The options `enableStyling` and `stylesSchema` no longer work. +You need to provide them using your own block schema. +If you are extending an existing one, you should add it as a normal `schemaEnhancer` modification. ``` See https://6.dev-docs.plone.org/volto/blocks/block-style-wrapper.html for more documentation. -### Remove Sentry integration from core +### Removed Sentry integration from core The Sentry integration was implemented in Volto core at a time when Volto did not provide a good add-on story. Since then, the add-on story has improved. It now makes sense to extract this feature into its own add-on. You can find it in [`@collective/volto-sentry`](https://github.com/collective/volto-sentry). +```{versionchanged} 16.0.0.alpha.45 +``` + ### Upgrade `husky` to latest version -In the case that you are using husky in your projects (like Volto does), you will have to adapt to the new way that `husky` has for defining hooks. +In case you use `husky` in your projects (like Volto does), you must adapt to the new way that `husky` defines hooks. -You'll have to add a script in your `package.json` file called `prepare`: +You will have to add a script in your `package.json` file called `prepare`: ```diff "build": "razzle build --noninteractive", @@ -519,36 +538,40 @@ You'll have to add a script in your `package.json` file called `prepare`: "test": "razzle test --maxWorkers=50%", ``` -After execute it, `husky` will install itself in the `.husky` folder of your project. Then you need to create the default hook scripts in `.husky` that you want to execute. You can copy over the Volto ones (take a look in Volto's `.husky` folder). +After executing it, `husky` will install itself in the `.husky` folder of your project. +Then you need to create the default hook scripts in `.husky` that you want to execute. +You can copy over the Volto ones (take a look in Volto's `.husky` folder). ### Better defaults handling ````{versionadded} 16.0.0-alpha.51 -Prior to this version we had a default values handling in schemas for blocks settings that was faulty and buggy. The state inferred was not deterministic and depending on the fields with defaults present. - -In order to correct this and allow Volto to handle defaults in a correct way we have to pass down an additional prop to the `BlockDataForm` like this: - - ```{code-block} jsx - :linenos: - :emphasize-lines: 10 - { - this.props.onChangeBlock(this.props.block, { - ...this.props.data, - [id]: value, - }); - }} - onChangeBlock={onChangeBlock} - formData={this.props.data} - block={block} - /> - ``` +Prior to this version, we handled default values in schemas for blocks settings in a faulty and buggy manner. +The state inferred was not deterministic and depended on the fields with defaults present. + +To correct this and allow Volto to handle defaults in a correct way, we have to pass down an additional prop to the `BlockDataForm` like this: + + ```jsx + :linenos: + :emphasize-lines: 10 + { + this.props.onChangeBlock(this.props.block, { + ...this.props.data, + [id]: value, + }); + }} + onChangeBlock={onChangeBlock} + formData={this.props.data} + block={block} + /> + ``` -whenever we use it in our custom or add-ons code. +…whenever we use it in our custom or add-on code. ```` + (volto-upgrade-guide-15.x.x)= ## Upgrading to Volto 15.x.x @@ -577,31 +600,31 @@ DraftJS libraries are now lazy-loaded, and some changes have been introduced in The old way: ```js - export default function applyConfig(config) { - config.settings = { - ...config.settings, - listBlockTypes = [ - ...config.settings.listBlockTypes, - 'my-list-item', - ] - } - return config; +export default function applyConfig(config) { + config.settings = { + ...config.settings, + listBlockTypes = [ + ...config.settings.listBlockTypes, + 'my-list-item', + ] } + return config; +} ``` The new way: ```js - export default function applyConfig(config) { - const { richtextEditorSettings } = config.settings; - config.settings.richtextEditorSettings = (props) => { - const result = richtextEditorSettings(props); - result.listBlockTypes = [...result.listBlockTypes, 'my-list-item'] - return result; - } - return config; +export default function applyConfig(config) { + const { richtextEditorSettings } = config.settings; + config.settings.richtextEditorSettings = (props) => { + const result = richtextEditorSettings(props); + result.listBlockTypes = [...result.listBlockTypes, 'my-list-item'] + return result; } + return config; +} ``` ### Language Switcher no longer takes care of the sync of the language @@ -611,7 +634,8 @@ Not doing so won't break your project, but they won't get the latest features an ### LinkView component markup change -The `LinkView` component has the literal `The link address is: ` is now wrapped in a `

` block instead of a `` block. Please check if you have a CSS bound to that node and adjust accordingly. +The `LinkView` component with the literal `The link address is: ` is now wrapped in a `

` block instead of a `` block. +Please check if you have a CSS bound to that node and adjust accordingly. ### Rename core-sandbox fixture to coresandbox From 351955a0fa7cc864d55a3fbbd286432d93a0c11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20S=C3=BCss?= Date: Sun, 20 Nov 2022 10:34:13 +0100 Subject: [PATCH 052/326] Update documentation of Sentry integration. (#3927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update documentation of Sentry integration. * Update docs/source/configuration/settings-reference.md Co-authored-by: Steve Piercy Co-authored-by: Steve Piercy Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 1 + docs/source/configuration/settings-reference.md | 8 ++++++++ docs/source/deploying/sentry.md | 10 +++++----- docs/source/upgrade-guide/index.md | 4 ++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f4ba8628..a8d487f43d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Documentation +- Document `Sentry` integration move from Volto core to add-on `@plone-collective/volto-sentry` in configuration, upgrade and deployment. @ksuess - Remove `sentryOptions` from settings reference. Clean up `deploying/sentry.md`. @stevepiercy - Tidy up `upgrade-guide/index.md`. @stevepiercy diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index 21d62772dc..b6c5254e5b 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -27,6 +27,14 @@ navDepth defaultBlockType The default block type in Volto is "text", which uses the current DraftJS-based implementation for the rich text editor. Future alternative rich text editors will need to use this setting and replace it with their block type. The block definition should also include the `blockHasValue` function, which is needed to activate the Block Chooser functionality. See this function signature in [Blocks > Settings](../blocks/settings.md). + + +sentryOptions + In Volto 16.0.0.alpha.45, Sentry integration was moved from core to the add-on [`@plone-collective/volto-sentry`](https://www.npmjs.com/package/@plone-collective/volto-sentry). + + ```{seealso} + See {doc}`../deploying/sentry` for Sentry integration. + ``` contentIcons With this property you can configure Content Types icons. diff --git a/docs/source/deploying/sentry.md b/docs/source/deploying/sentry.md index e019d4e102..c2edd27011 100644 --- a/docs/source/deploying/sentry.md +++ b/docs/source/deploying/sentry.md @@ -15,10 +15,10 @@ Sentry is a monitoring platform that can help identify the cause of errors in yo ## Prerequisities -1. Install the add-on [`volto-sentry`](https://github.com/collective/volto-sentry). -1. In Sentry, create a new organization, and add a project to it. -1. On the projects settings page, from {guilabel}`Client Keys (DSN)`, take the `SENTRY_DSN`. -1. Create an API Token: in the top-left corner, click on {guilabel}`your name -> API keys`, and create a new token. +1. Install the add-on [`@plone-collective/volto-sentry`](https://www.npmjs.com/package/@plone-collective/volto-sentry). +2. In Sentry, create a new organization, and add a project to it. +3. On the projects settings page, from {guilabel}`Client Keys (DSN)`, take the `SENTRY_DSN`. +4. Create an API Token: in the top-left corner, click on {guilabel}`your name -> API keys`, and create a new token. {guilabel}`project:write` scope should be selected. ```{note} @@ -26,7 +26,7 @@ Instructions tested with Sentry 9.1.2. ``` ```{versionchanged} 16.0.0.alpha.45 -Sentry was removed from Volto core and into a separate add-on [`volto-sentry`](https://github.com/collective/volto-sentry) in 16.0.0.alpha.45. +Sentry was moved from Volto core and into a separate add-on [`volto-sentry`](https://github.com/collective/volto-sentry). ``` diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 5cd7ec632c..ee3948baca 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -516,12 +516,12 @@ If you are extending an existing one, you should add it as a normal `schemaEnhan See https://6.dev-docs.plone.org/volto/blocks/block-style-wrapper.html for more documentation. -### Removed Sentry integration from core +### Sentry integration moved from Volto core to add-on The Sentry integration was implemented in Volto core at a time when Volto did not provide a good add-on story. Since then, the add-on story has improved. It now makes sense to extract this feature into its own add-on. -You can find it in [`@collective/volto-sentry`](https://github.com/collective/volto-sentry). +Integrate Sentry in your app with [`@plone-collective/volto-sentry`](https://www.npmjs.com/package/@plone-collective/volto-sentry). ```{versionchanged} 16.0.0.alpha.45 ``` From 1cf9a0eec023dcfcf7eab78721a31223ec0d9233 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 20 Nov 2022 10:53:26 +0100 Subject: [PATCH 053/326] Prepare for release --- packages/generator-volto/CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 566f949740..7c2a5358e1 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -2,16 +2,10 @@ ## 6.0.0 (unreleased) -### Breaking - -### Feature - ### Bugfix - Generator is aware of `rc`, `beta` and `alphas` as possible releases for canary @sneridagh -### Internal - ## 6.0.0-alpha.3 (2022-11-16) ### Bugfix From 40b34a295e254372f7602a90906d9ac002645e23 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 20 Nov 2022 10:53:52 +0100 Subject: [PATCH 054/326] Release generate-volto 6.0.0-alpha.4 --- packages/generator-volto/CHANGELOG.md | 2 +- packages/generator-volto/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 7c2a5358e1..39138fca17 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 6.0.0 (unreleased) +## 6.0.0-alpha.4 (2022-11-20) ### Bugfix diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index 9f58c6f975..080bc5b000 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "6.0.0-alpha.3", + "version": "6.0.0-alpha.4", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" From d06c2cf183c849f3f441f127a1d1d0c2df9ffd2f Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sun, 20 Nov 2022 10:53:58 +0100 Subject: [PATCH 055/326] Back to development (generator-volto) --- packages/generator-volto/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 39138fca17..76f30eaf1f 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## 6.0.0 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + ## 6.0.0-alpha.4 (2022-11-20) ### Bugfix From 64738b8c4a3dc5ccb9b28734eeb6d55c8d924edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 20 Nov 2022 12:01:05 +0100 Subject: [PATCH 056/326] Update to Plone 6 RC1 (#3924) * Update to Plone 6 RC1 * Fix tests --- CHANGELOG.md | 2 ++ Makefile | 10 +++++----- api/buildout.cfg | 2 +- api/versions.cfg | 4 ++-- cypress/tests/core/basic/sharing.js | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d487f43d..416959b65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### Internal +- Update to Plone 6 RC1 @sneridagh + ### Documentation - Document `Sentry` integration move from Volto core to add-on `@plone-collective/volto-sentry` in configuration, upgrade and deployment. @ksuess diff --git a/Makefile b/Makefile index 254e3818e5..7af36a552f 100644 --- a/Makefile +++ b/Makefile @@ -13,14 +13,14 @@ MAKEFLAGS+=--no-builtin-rules # Project settings INSTANCE_PORT=8080 -DOCKER_IMAGE=plone/plone-backend:6.0.0b3 -KGS=plone.restapi==8.32.0 plone.volto==4.0.0a14 -TESTING_ADDONS=plone.app.robotframework==2.0.0b2 plone.app.testing==7.0.0a3 +DOCKER_IMAGE=plone/plone-backend:6.0.0rc1 +KGS= +TESTING_ADDONS=plone.app.robotframework==2.0.0b2 plone.app.testing==7.0.0b2 NODEBIN = ./node_modules/.bin # Plone 5 legacy -DOCKER_IMAGE5=plone/plone-backend:5.2.9 -KGS5=plone.restapi==8.32.0 plone.volto==4.0.0a14 plone.rest==2.0.0 +DOCKER_IMAGE5=plone/plone-backend:5.2.10 +KGS5=plone.restapi==8.32.2 plone.volto==4.0.0 plone.rest==2.0.0 # Sphinx variables # You can set these variables from the command line. diff --git a/api/buildout.cfg b/api/buildout.cfg index fba6d75667..ff3e424784 100644 --- a/api/buildout.cfg +++ b/api/buildout.cfg @@ -1,7 +1,7 @@ [buildout] index = https://pypi.org/simple/ extends = - http://dist.plone.org/release/6.0.0b3/versions.cfg + http://dist.plone.org/release/6.0.0rc1/versions.cfg version-constraints.cfg versions.cfg parts = instance plonesite site-packages test robot-server diff --git a/api/versions.cfg b/api/versions.cfg index 2541a60e2c..4663d89fd5 100644 --- a/api/versions.cfg +++ b/api/versions.cfg @@ -5,8 +5,8 @@ async-generator = 1.10 collective.folderishtypes = 3.0.0 collective.recipe.plonesite = 1.12.0 h11 = 0.12.0 -plone.restapi = 8.32.0 -plone.volto = 4.0.0a13 +plone.restapi = 8.32.2 +plone.volto = 4.0.0 prompt-toolkit = 2.0.10 pyOpenSSL = 21.0.0 robotframework-debuglibrary = 2.2.2 diff --git a/cypress/tests/core/basic/sharing.js b/cypress/tests/core/basic/sharing.js index de532dacf2..423f1e5beb 100644 --- a/cypress/tests/core/basic/sharing.js +++ b/cypress/tests/core/basic/sharing.js @@ -67,7 +67,7 @@ describe('Sharing Tests', () => { cy.visit('/logout'); cy.wait('@logout'); - cy.autologin('test-user'); + cy.autologin('test-user', 'correct horse battery staple'); cy.visit('/my-page'); cy.findByRole('heading', { name: /my page/i }).should('exist'); }); From c8dea8d7114c807eb5d524b9c4201f775de4cdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 20 Nov 2022 12:58:39 +0100 Subject: [PATCH 057/326] Fix image tag for Plone 5.2.x, use 5.2.9 (#3936) --- CHANGELOG.md | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 416959b65d..dfdd994e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Bugfix - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim +- Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh ### Internal diff --git a/Makefile b/Makefile index 7af36a552f..e60efcb688 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ TESTING_ADDONS=plone.app.robotframework==2.0.0b2 plone.app.testing==7.0.0b2 NODEBIN = ./node_modules/.bin # Plone 5 legacy -DOCKER_IMAGE5=plone/plone-backend:5.2.10 +DOCKER_IMAGE5=plone/plone-backend:5.2.9 KGS5=plone.restapi==8.32.2 plone.volto==4.0.0 plone.rest==2.0.0 # Sphinx variables From ec20a12759123a18b1b84c4767dd6dc09e7b4a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 20 Nov 2022 15:55:49 +0100 Subject: [PATCH 058/326] More block defaults edge cases (#3937) * Add filled in data test * Fix edge case when data is passed with values * Changelog Co-authored-by: Tiberiu Ichim --- CHANGELOG.md | 1 + src/helpers/Blocks/Blocks.test.js | 50 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfdd994e1d..eee895a6ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim - Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh +- Cover an additional edge case for defaults @tiberiuichim ### Internal diff --git a/src/helpers/Blocks/Blocks.test.js b/src/helpers/Blocks/Blocks.test.js index a7f2281b30..d31988ddc2 100644 --- a/src/helpers/Blocks/Blocks.test.js +++ b/src/helpers/Blocks/Blocks.test.js @@ -742,6 +742,56 @@ describe('Blocks', () => { }, }); }); + + it('Sets data according to schema default values, keeps existing data', () => { + const schema = { + properties: { + style: { + widget: 'object', + schema: { + title: 'Style', + fieldsets: [ + { + id: 'default', + fields: ['color', 'theme'], + title: 'Default', + }, + ], + properties: { + color: { + title: 'Color', + default: 'red', + }, + theme: { + title: 'Theme', + default: 'primary', + }, + }, + required: [], + }, + }, + }, + }; + + expect( + applySchemaDefaults({ + schema, + data: { + '@type': 'slider', + style: { + theme: 'secondary', + }, + }, + intl: {}, + }), + ).toEqual({ + '@type': 'slider', + style: { + color: 'red', + theme: 'secondary', + }, + }); + }); }); describe('applyBlockDefaults', () => { From d1b985e3bb53d0cbca58f396e288a3db572ef290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 20 Nov 2022 19:03:43 +0100 Subject: [PATCH 059/326] Support for drilled down current state and updater function from schema in `ObjectListWidget` (#3939) --- CHANGELOG.md | 2 ++ .../manage/Widgets/ObjectListWidget.jsx | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eee895a6ec..c8067a459c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Feature +- Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh + ### Bugfix - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim diff --git a/src/components/manage/Widgets/ObjectListWidget.jsx b/src/components/manage/Widgets/ObjectListWidget.jsx index b700c90cfd..c2d5481551 100644 --- a/src/components/manage/Widgets/ObjectListWidget.jsx +++ b/src/components/manage/Widgets/ObjectListWidget.jsx @@ -57,7 +57,9 @@ const messages = defineMessages({ * } * mutated.fieldsets[0].fields.push('extraField'); * return mutated; - * } + * }, + * activeObject: 0, // Current active object drilled down from the schema (if present) + * setActiveObject: () => {} // The current active object state updater function drilled down from the schema (if present) * }, * ``` */ @@ -71,7 +73,22 @@ const ObjectListWidget = (props) => { onChange, schemaExtender, } = props; - const [activeObject, setActiveObject] = React.useState(value.length - 1); + const [localActiveObject, setLocalActiveObject] = React.useState( + props.activeObject ?? value.length - 1, + ); + + let activeObject, setActiveObject; + if ( + (props.activeObject || props.activeObject === 0) && + props.setActiveObject + ) { + activeObject = props.activeObject; + setActiveObject = props.setActiveObject; + } else { + activeObject = localActiveObject; + setActiveObject = setLocalActiveObject; + } + const intl = useIntl(); function handleChangeActiveObject(e, blockProps) { From 5eb6fba6427e4847c5ac5632fba34aeeae11992f Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 20 Nov 2022 12:26:21 -0800 Subject: [PATCH 060/326] Fix some MyST syntax and English grammar --- CHANGELOG.md | 1 + .../configuration/settings-reference.md | 4 +- docs/source/upgrade-guide/index.md | 62 ++++++++++--------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8067a459c..6d9fef733c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Document `Sentry` integration move from Volto core to add-on `@plone-collective/volto-sentry` in configuration, upgrade and deployment. @ksuess - Remove `sentryOptions` from settings reference. Clean up `deploying/sentry.md`. @stevepiercy - Tidy up `upgrade-guide/index.md`. @stevepiercy +- Fix some MyST syntax and English grammar. @stevepiercy ## 16.0.0-rc.1 (2022-11-18) diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index b6c5254e5b..6b5f07a085 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -27,13 +27,13 @@ navDepth defaultBlockType The default block type in Volto is "text", which uses the current DraftJS-based implementation for the rich text editor. Future alternative rich text editors will need to use this setting and replace it with their block type. The block definition should also include the `blockHasValue` function, which is needed to activate the Block Chooser functionality. See this function signature in [Blocks > Settings](../blocks/settings.md). - + sentryOptions In Volto 16.0.0.alpha.45, Sentry integration was moved from core to the add-on [`@plone-collective/volto-sentry`](https://www.npmjs.com/package/@plone-collective/volto-sentry). ```{seealso} - See {doc}`../deploying/sentry` for Sentry integration. + See {doc}`../deploying/sentry`. ``` contentIcons diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index ee3948baca..80919b8419 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -18,9 +18,10 @@ For more information see {ref}`volto-versioning-policy`. ```{note} There are times when updating the Volto boilerplate (the one generated by `@plone/generator-volto`) is enough to fulfill all the changes. If you have not heavily modified it, moving things around and copying over your dependencies might do when dealing with upgrades. -We keep the generator up to date and in sync with current Volto release. -Please notice that the generator is able to tell you when it runs if it's outdated. -The generator is also able to "update" your project with the latest changes, and propose to you to merge the changes, so you can run it on top of your project by answering the prompts. +We keep the generator up to date and in sync with the current Volto release. +When it runs, the generator will tell you whether it is outdated. +The generator will also "update" your project with the latest changes, and propose to merge the changes. +Thus it is safe to run it on top of your project and answer the prompts. ``` @@ -166,17 +167,17 @@ A few updates may be needed in existing projects: 6. If you have customized webpack loader configuration related to CSS, make sure it is updated to be compatible with PostCSS 8. 7. It's recommended that you remove your existing `node_modules` and start clean. -8. If the add-ons you are using are not yet updated to latest `@plone/scripts`, it's also recommended that you force the version in your build, setting this in `package.json`: +8. If the add-ons you are using are not yet updated to the latest `@plone/scripts`, it's also recommended that you force the version in your build, setting this in `package.json`: ```json - "resolutions": { - "**/@plone/scripts": "^2.0.0" - } + "resolutions": { + "**/@plone/scripts": "^2.0.0" + } ``` #### Upgrade and update add-ons dependency on `@plone/scripts` -```{warning} +```{note} This applies only to Volto add-ons. ``` @@ -230,7 +231,7 @@ We updated Volto to be able to use it, however some changes have to be made in y 1. Enable yarn 3 in your project, adding `.yarnrc.yml`: - ```yml + ```yaml defaultSemverRangePrefix: "" nodeLinker: node-modules @@ -353,7 +354,7 @@ This dramatically improves the launch and test times in these machines. It also includes the new "component" testing feature that might be appealing in the near future. The only drawback is that they also overhauled the configuration, forcing migration from old configuration based on JSON files to a better JavaScript-based one. They also changed and renamed some options. -Luckily, Cypress provides good reporting when old configuration is in place, and an interactive migration wizard. +Luckily, Cypress provides both good reporting when an old configuration is in place, and an interactive migration wizard. Core configuration has been updated, but you will need to update your Cypress configuration if you want to use core's Cypress 10. It is possible that forcing your project to use older versions might still work with old configurations. @@ -514,7 +515,9 @@ You need to provide them using your own block schema. If you are extending an existing one, you should add it as a normal `schemaEnhancer` modification. ``` +```{seealso} See https://6.dev-docs.plone.org/volto/blocks/block-style-wrapper.html for more documentation. +``` ### Sentry integration moved from Volto core to add-on @@ -548,27 +551,26 @@ You can copy over the Volto ones (take a look in Volto's `.husky` folder). Prior to this version, we handled default values in schemas for blocks settings in a faulty and buggy manner. The state inferred was not deterministic and depended on the fields with defaults present. -To correct this and allow Volto to handle defaults in a correct way, we have to pass down an additional prop to the `BlockDataForm` like this: - - ```jsx - :linenos: - :emphasize-lines: 10 - { - this.props.onChangeBlock(this.props.block, { - ...this.props.data, - [id]: value, - }); - }} - onChangeBlock={onChangeBlock} - formData={this.props.data} - block={block} - /> - ``` +To correct this and allow Volto to handle defaults in a correct way, we have to pass down an additional prop to the `BlockDataForm` whenever we use it in our custom or add-on code, as in the following example. + +```{code-block} jsx +:linenos: +:emphasize-lines: 10 -…whenever we use it in our custom or add-on code. + { + this.props.onChangeBlock(this.props.block, { + ...this.props.data, + [id]: value, + }); + }} + onChangeBlock={onChangeBlock} + formData={this.props.data} + block={block} + /> +``` ```` From c06ab0ff0be0f979e8c4221c6ad2915972a4ab50 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 21 Nov 2022 11:20:25 +0200 Subject: [PATCH 061/326] Introduce settings.styleClassNameConverters (#3921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Introduce settings.styleClassNameConverters * Enable the converters tests * Add export * Add test to show multiple nested levels * Add settings docs * A bit more docs * Changelog * Add Style config module * Simplify code * Fix prefix suffix * Show edge case * Add one more level * Fix edge cases; don't return empty classnames * Better fix * Make code readable * Fix ESlint, remove not used import Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 1 + docs/source/blocks/block-style-wrapper.md | 32 +++++++- .../configuration/settings-reference.md | 14 +++- src/config/Style.jsx | 9 +++ src/config/index.js | 2 + src/helpers/Blocks/Blocks.js | 75 +++++++++---------- src/helpers/Blocks/Blocks.test.js | 64 ++++++++++++++++ test-setup-config.js | 2 + 8 files changed, 155 insertions(+), 44 deletions(-) create mode 100644 src/config/Style.jsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d9fef733c..0641f667cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Feature +- Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim - Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh ### Bugfix diff --git a/docs/source/blocks/block-style-wrapper.md b/docs/source/blocks/block-style-wrapper.md index 8420f4f9f3..548d890455 100644 --- a/docs/source/blocks/block-style-wrapper.md +++ b/docs/source/blocks/block-style-wrapper.md @@ -62,7 +62,7 @@ const stylingSchema = { } ``` -The `addStyling` helper adds the (empty) `styles` field inside the `Styling` fieldset for you, so when defining your block schema you can do: +The `addStyling` schemaEnhancer adds the (empty) `styles` field inside the `Styling` fieldset for you, so when defining your block schema you can do: ```js import { addStyling } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer'; @@ -90,6 +90,19 @@ export const TeaserSchema = ({ intl }) => { You can add a set of style fields defining your block styles—such as alignment, background color, and so on—in your block schema by adding them to the `styles` object field as shown above. +In case you decide to create reusable sets of styling controls for your blocks, you can use the `composeSchema` helper to compose multiple schemaEnhancers: + +```js +import { composeSchema } from '@plone/volto/helpers'; + +// ... +config.blocks.blocksConfig.listing.schemaEnhancer = composeSchema( + config.blocks.blocksConfig.listing.schemaEnhancer, + addStyling, + addMyCustomColorStylingField, +) +``` + ## The `styles` field The `styles` field is mapped to an `objectWidget`. @@ -132,9 +145,24 @@ The resultant HTML would be the following: ```html

``` - Then it's at your discretion how you define the CSS class names in your theme. +If you need other style of classnames generated, you can use the classname +converters defined in `config.settings.styleClassNameConverters`, by +registering fieldnames suffixed with the converter name. For example, a style +data like: + +``` +{ + "styles": { + "theme:noprefix": "primary", + "inverted:bool": true, + } +} +``` + +will generate classnames `primary inverted` + ## Align class injection There is an automatic class name injection happening at the same time the style wrapper class names injection. diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index 6b5f07a085..2aa64b3f58 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -203,6 +203,10 @@ controlpanels filterControlPanelsSchema A schema factory for a control panel. It is used internally, to tweak the schemas provided by the controlpanel endpoint, to make them fit for Volto. +errorHandlers + A list of error handlers that will be called when there is an unhandled exception. Each error handler is a function that + receives a single argument, the `error` object. + workflowMapping It's an object that defines the mapping between workflow states/transitions and the color that should show in the change Workflow dropdown. This is the default: @@ -222,10 +226,12 @@ workflowMapping It's meant to be extended with your own workflows/transitions. It is recommended to assign the same color to the transition as the destination state, so the user can have the visual hint to which state are they transitioning to. -errorHandlers - A list of error handlers that will be called when there is an unhandled exception. Each error handler is a function that - receives a single argument, the `error` object. -``` + +styleClassNameConverters + An object with functions used by the style wrapper helpers to convert style + data to actual class names. You can customize the generated classname by + registering fieldnames with names such as `:`, + where the converter is registered here. ## Views settings diff --git a/src/config/Style.jsx b/src/config/Style.jsx new file mode 100644 index 0000000000..6d5239cb3e --- /dev/null +++ b/src/config/Style.jsx @@ -0,0 +1,9 @@ +export const styleClassNameConverters = { + default: (name, value, prefix = '') => { + return value + ? `has--${prefix}${name}--${(value || '').toString().replace(/^#/, '')}` + : null; + }, + noprefix: (name, value) => value, + bool: (name, value) => (value ? name : ''), +}; diff --git a/src/config/index.js b/src/config/index.js index fe754dca0a..a39657a0ce 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -24,6 +24,7 @@ import { loadables } from './Loadables'; import { workflowMapping } from './Workflows'; import { contentIcons } from './ContentIcons'; +import { styleClassNameConverters } from './Style'; import { controlPanelsIcons, filterControlPanels, @@ -167,6 +168,7 @@ let config = { addonsInfo: addonsInfo, workflowMapping, errorHandlers: [], // callables for unhandled errors + styleClassNameConverters, }, experimental: { addBlockButton: { diff --git a/src/helpers/Blocks/Blocks.js b/src/helpers/Blocks/Blocks.js index b6b6d0b2c0..9ecbd1e210 100644 --- a/src/helpers/Blocks/Blocks.js +++ b/src/helpers/Blocks/Blocks.js @@ -3,16 +3,7 @@ * @module helpers/Blocks */ -import { - omit, - without, - endsWith, - find, - isObject, - keys, - toPairs, - merge, -} from 'lodash'; +import { omit, without, endsWith, find, isObject, keys, merge } from 'lodash'; import move from 'lodash-move'; import { v4 as uuid } from 'uuid'; import config from '@plone/volto/registry'; @@ -450,35 +441,43 @@ export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) { return applySchemaDefaults({ data, schema, intl }); } -export const buildStyleClassNamesFromData = (styles) => { - // styles has the form +/** + * Converts a name+value style pair (ex: color/red) to a classname, + * such as "has--color--red" + * + * This can be expanded via the style names, by suffixing them with special + * converters. See config.settings.styleClassNameConverters. Examples: + * + * styleToClassName('theme:noprefix', 'primary') returns "primary" + * styleToClassName('inverted:bool', true) returns 'inverted' + * styleToClassName('inverted:bool', false) returns '' + */ +export const styleToClassName = (key, value, prefix = '') => { + const converters = config.settings.styleClassNameConverters; + const [name, ...convIds] = key.split(':'); + + return (convIds.length ? convIds : ['default']) + .map((id) => converters[id]) + .reduce((acc, conv) => conv(acc, value, prefix), name); +}; + +export const buildStyleClassNamesFromData = (obj = {}, prefix = '') => { + // styles has the form: // const styles = { - // color: 'red', - // backgroundColor: '#AABBCC', + // color: 'red', + // backgroundColor: '#AABBCC', // } // Returns: ['has--color--red', 'has--backgroundColor--AABBCC'] - let styleArray = []; - const pairedStyles = toPairs(styles); - pairedStyles.forEach((item) => { - if (isObject(item[1])) { - const flattenedNestedStyles = toPairs(item[1]).map((nested) => [ - item[0], - ...nested, - ]); - flattenedNestedStyles.forEach((sub) => styleArray.push(sub)); - } else { - styleArray.push(item); - } - }); - return styleArray.map((item) => { - const classname = item.map((item) => { - const str_item = item ? item.toString() : ''; - return str_item && str_item.startsWith('#') - ? str_item.replace('#', '') - : str_item; - }); - return `has--${classname[0]}--${classname[1]}${ - classname[2] ? `--${classname[2]}` : '' - }`; - }); + + return Object.entries(obj) + .reduce( + (acc, [k, v]) => [ + ...acc, + ...(isObject(v) + ? buildStyleClassNamesFromData(v, `${prefix}${k}--`) + : [styleToClassName(k, v, prefix)]), + ], + [], + ) + .filter((v) => !!v); }; diff --git a/src/helpers/Blocks/Blocks.test.js b/src/helpers/Blocks/Blocks.test.js index d31988ddc2..097a0aa292 100644 --- a/src/helpers/Blocks/Blocks.test.js +++ b/src/helpers/Blocks/Blocks.test.js @@ -869,6 +869,7 @@ describe('Blocks', () => { expect(applyBlockDefaults({ data })).toEqual({}); }); }); + describe('buildStyleClassNamesFromData', () => { it('Sets styles classname array according to style values', () => { const styles = { @@ -880,6 +881,7 @@ describe('Blocks', () => { 'has--backgroundColor--AABBCC', ]); }); + it('Sets styles classname array according to style values with nested', () => { const styles = { color: 'red', @@ -896,6 +898,7 @@ describe('Blocks', () => { 'has--nested--bar--black', ]); }); + it('Sets styles classname array according to style values with nested and colors', () => { const styles = { color: 'red', @@ -913,6 +916,27 @@ describe('Blocks', () => { ]); }); + it('Supports multiple nested level', () => { + const styles = { + color: 'red', + backgroundColor: '#AABBCC', + nested: { + l1: 'white', + level2: { + foo: '#fff', + bar: '#000', + }, + }, + }; + expect(buildStyleClassNamesFromData(styles)).toEqual([ + 'has--color--red', + 'has--backgroundColor--AABBCC', + 'has--nested--l1--white', + 'has--nested--level2--foo--fff', + 'has--nested--level2--bar--000', + ]); + }); + it('Sets styles classname array according to style values with int values', () => { const styles = { color: 'red', @@ -923,5 +947,45 @@ describe('Blocks', () => { 'has--borderRadius--8', ]); }); + + it('Understands noprefix converter for style values', () => { + const styles = { + color: 'red', + 'theme:noprefix': 'primary', + }; + expect(buildStyleClassNamesFromData(styles)).toEqual([ + 'has--color--red', + 'primary', + ]); + }); + + it('Understands bool converter for trueish value', () => { + const styles = { + color: 'red', + 'inverted:bool': true, + }; + expect(buildStyleClassNamesFromData(styles)).toEqual([ + 'has--color--red', + 'inverted', + ]); + }); + + it('Understands bool converter for false value', () => { + const styles = { + color: 'red', + 'inverted:bool': false, + }; + expect(buildStyleClassNamesFromData(styles)).toEqual(['has--color--red']); + }); + + it('Ugly edge cases', () => { + const styles = { + color: undefined, + nested: { + l1: {}, + }, + }; + expect(buildStyleClassNamesFromData(styles)).toEqual([]); + }); }); }); diff --git a/test-setup-config.js b/test-setup-config.js index 350f0f90a3..029669989b 100644 --- a/test-setup-config.js +++ b/test-setup-config.js @@ -20,6 +20,7 @@ import { } from '@plone/volto/config/RichTextEditor/Blocks'; import FromHTMLCustomBlockFn from '@plone/volto/config/RichTextEditor/FromHTML'; import { contentIcons } from '@plone/volto/config/ContentIcons'; +import { styleClassNameConverters } from '@plone/volto/config/Style'; import { controlPanelsIcons, @@ -74,6 +75,7 @@ config.set('settings', { apiExpanders: [], downloadableObjects: ['File'], viewableInBrowserObjects: [], + styleClassNameConverters, }); config.set('blocks', { blocksConfig: { From f72442e85ce76997bb63609b2b93484dfef999c5 Mon Sep 17 00:00:00 2001 From: Alok Kumar Date: Mon, 21 Nov 2022 20:37:10 +0530 Subject: [PATCH 062/326] Fix translation spelling of toggle (#3949) --- CHANGELOG.md | 1 + locales/de/LC_MESSAGES/volto.po | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0641f667cd..ee14655ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim - Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh - Cover an additional edge case for defaults @tiberiuichim +- Fix translation spelling of toggle @iFlameing ### Internal diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index 3d79cb8537..6a46175496 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -4463,7 +4463,7 @@ msgstr "Inhaltsverzeichnis" #: config/Blocks # defaultMessage: Toggle msgid "toggleFacet" -msgstr "Toogle" +msgstr "Toggle" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Update from version {origin} to {destination} From df09acc90d55bf611d677d6086b096c12cbaaa40 Mon Sep 17 00:00:00 2001 From: Rob Gietema Date: Mon, 21 Nov 2022 17:04:22 +0100 Subject: [PATCH 063/326] Fix issue when using list markdown when list is already active. (#3946) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 2 +- .../src/editor/plugins/Markdown/constants.js | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee14655ec8..610bbf98ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim - Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh - Cover an additional edge case for defaults @tiberiuichim +- Fix issue when using list markdown when list is already active (volto-slate) @robgietema - Fix translation spelling of toggle @iFlameing ### Internal @@ -27,7 +28,6 @@ - Tidy up `upgrade-guide/index.md`. @stevepiercy - Fix some MyST syntax and English grammar. @stevepiercy - ## 16.0.0-rc.1 (2022-11-18) ### Feature diff --git a/packages/volto-slate/src/editor/plugins/Markdown/constants.js b/packages/volto-slate/src/editor/plugins/Markdown/constants.js index ccc6b70ef6..9a7f1c55d3 100644 --- a/packages/volto-slate/src/editor/plugins/Markdown/constants.js +++ b/packages/volto-slate/src/editor/plugins/Markdown/constants.js @@ -15,17 +15,6 @@ export const localToggleList = (editor, format) => { }); }; -/** - * @summary Turns off any list type. - * @param {Editor} editor The editor to which to apply the change. - * @returns The result of the inner call to the function `unwrapList`. - */ -const preFormat = (editor) => { - return unwrapList(editor, false, { - unwrapFromList: false, - }); -}; - /** * The autoformat rules created by this plugin for the Markdown language. * @@ -35,17 +24,14 @@ export const autoformatRules = [ { type: 'h2', markup: '#', - // preFormat, }, { type: 'h3', markup: '##', - // preFormat, }, { type: LI, markup: ['*', '-', '+'], - preFormat, format: (editor) => { localToggleList(editor, 'ul'); }, @@ -53,7 +39,6 @@ export const autoformatRules = [ { type: LI, markup: ['1.', '1)'], - preFormat, format: (editor) => { localToggleList(editor, 'ol'); }, From 54dfaff35d08b50e946f25f37a6400103940e971 Mon Sep 17 00:00:00 2001 From: Alok Kumar Date: Tue, 22 Nov 2022 10:05:12 +0530 Subject: [PATCH 064/326] Fix keyboard accessibility issue of Clear button in Folder content view (#3892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 1 + src/components/manage/Contents/Contents.jsx | 15 ++++++++++----- .../Contents/__snapshots__/Contents.test.jsx.snap | 1 + theme/themes/pastanaga/extras/blocks.less | 2 +- theme/themes/pastanaga/extras/contents.less | 8 ++++++++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 610bbf98ab..898627415e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim - Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh - Cover an additional edge case for defaults @tiberiuichim +- Fix keyboard accessibility issue of Clear button in Folder content view @iFlameing - Fix issue when using list markdown when list is already active (volto-slate) @robgietema - Fix translation spelling of toggle @iFlameing diff --git a/src/components/manage/Contents/Contents.jsx b/src/components/manage/Contents/Contents.jsx index 79a0f7bed6..fb0b203ef8 100644 --- a/src/components/manage/Contents/Contents.jsx +++ b/src/components/manage/Contents/Contents.jsx @@ -1509,20 +1509,25 @@ class Contents extends Component { onChange={this.onChangeFilter} /> {this.state.filter && ( - { this.onChangeFilter('', { value: '' }); }} - /> + > + + )}
diff --git a/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap b/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap index 387861269f..c3a39f50c6 100644 --- a/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap +++ b/src/components/manage/Contents/__snapshots__/Contents.test.jsx.snap @@ -323,6 +323,7 @@ exports[`Contents renders a folder contents view component 1`] = ` style={ Object { "fill": "#007eb1", + "flexShrink": "0", "height": "30px", "width": "auto", } diff --git a/theme/themes/pastanaga/extras/blocks.less b/theme/themes/pastanaga/extras/blocks.less index b79f9afac8..eddd31fecd 100644 --- a/theme/themes/pastanaga/extras/blocks.less +++ b/theme/themes/pastanaga/extras/blocks.less @@ -479,8 +479,8 @@ body.has-toolbar.has-sidebar-collapsed .ui.wrapper > .ui.inner.block.full { bottom: -26px; left: 50%; padding: 0 !important; - background: white !important; border: none !important; + background: white !important; border-radius: 50% !important; transform: translateX(-50%); } diff --git a/theme/themes/pastanaga/extras/contents.less b/theme/themes/pastanaga/extras/contents.less index ebfd3214e6..de819a07bb 100644 --- a/theme/themes/pastanaga/extras/contents.less +++ b/theme/themes/pastanaga/extras/contents.less @@ -57,6 +57,14 @@ .ui.menu .menu.top-menu-searchbox { border-left: 1px solid #b8c6c8; + + .ui.button.icon.icon-container { + padding: 0px; + + &:focus-visible { + outline: 1px solid #005fcc; + } + } } .ui.secondary.attached.segment.contents-breadcrumbs { From 2b61198c2d21a484e584e0a376172499ff774805 Mon Sep 17 00:00:00 2001 From: David Glick Date: Tue, 22 Nov 2022 01:40:25 -0800 Subject: [PATCH 065/326] Add docs page about experimental features (#3942) Co-authored-by: Steve Piercy --- docs/source/configuration/experimental.md | 36 +++++++++++++++++++++++ docs/source/configuration/index.md | 1 + 2 files changed, 37 insertions(+) create mode 100644 docs/source/configuration/experimental.md diff --git a/docs/source/configuration/experimental.md b/docs/source/configuration/experimental.md new file mode 100644 index 0000000000..103e8a1be9 --- /dev/null +++ b/docs/source/configuration/experimental.md @@ -0,0 +1,36 @@ +--- +myst: + html_meta: + "description": "Enable experimental features in Volto's configuration object" + "property=og:description": "Enable experimental features in Volto's configuration object" + "property=og:title": "Experimental features" + "keywords": "Volto, Plone, frontend, experimental, features" +--- + +# Experimental features + +Experimental features are planned for inclusion in a future release of Volto, +but are not yet considered mature by the community. +An experimental feature gives users an easy way to install it and see if anything breaks before we make a new stable release that includes it. + +## Volto configuration + +You can enable an experimental feature—also called a "feature flag"—in Volto's configuration object as shown below. + +```js +import config from '@plone/volto/registry' + +config.experimental.addBlockButton.enabled = true; +``` + +Currently the following experimental features are available: + +```{glossary} +:sorted: + +addBlockButton + Enables a new UI for adding blocks. + The button to add a block is now shown below any selected block. + A text block can also be added by clicking in the empty area at the bottom of the content area. + +``` diff --git a/docs/source/configuration/index.md b/docs/source/configuration/index.md index 421c36f0d3..fde6916ff3 100644 --- a/docs/source/configuration/index.md +++ b/docs/source/configuration/index.md @@ -14,6 +14,7 @@ myst: how-to settings-reference +experimental zero-config-builds component-registry internalproxy From f507f1cc8c94d5f75370474b648cc00f19f75a9a Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 22 Nov 2022 18:38:26 +0100 Subject: [PATCH 066/326] Prepare for release --- packages/generator-volto/CHANGELOG.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 76f30eaf1f..459cdd109f 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -2,14 +2,10 @@ ## 6.0.0 (unreleased) -### Breaking - -### Feature - -### Bugfix - ### Internal +- Releasing final @sneridagh + ## 6.0.0-alpha.4 (2022-11-20) ### Bugfix From 02de9f689b76f2ed24d41a1240a77e6b75422d56 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 22 Nov 2022 18:38:49 +0100 Subject: [PATCH 067/326] Release generate-volto 6.0.0 --- packages/generator-volto/CHANGELOG.md | 2 +- packages/generator-volto/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 459cdd109f..b79233ace1 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 6.0.0 (unreleased) +## 6.0.0 (2022-11-22) ### Internal diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index 080bc5b000..b0bd3cda2a 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "6.0.0-alpha.4", + "version": "6.0.0", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" From 3c8b70ba76d156b689475906951359d0cb497429 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 22 Nov 2022 18:38:55 +0100 Subject: [PATCH 068/326] Back to development (generator-volto) --- packages/generator-volto/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index b79233ace1..dbfcf0d64b 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## 6.0.1 (unreleased) + +### Breaking + +### Feature + +### Bugfix + +### Internal + ## 6.0.0 (2022-11-22) ### Internal From 11790d0e1d97ce2bcc72a76a1f0c10d8d6d1a227 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 22 Nov 2022 19:12:06 +0100 Subject: [PATCH 069/326] Sync CHANGELOG until release of 16.0.0 --- CHANGELOG.md | 377 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 376 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 898627415e..453c69e9e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 16.0.0 (unreleased) +## 16.0.1 (unreleased) ### Breaking @@ -11,13 +11,388 @@ ### Bugfix +### Internal + +### Documentation + +## 16.0.0 (2022-11-22) + +### Breaking + +- Deprecate NodeJS 12 since it's out of LTS since April 30, 2022 @sneridagh +- Move all cypress actions to the main `Makefile`, providing better meaningful names. Remove them from `package.json` script section. @sneridagh +- Remove `div` as default if `as` prop from `RenderBlocks`. Now the default is a `React.Fragment` instead. This could lead to CSS inconsistencies if taken this div into account, specially if used in custom add-ons without. In order to avoid them, set the `as` property always in your add-ons. @sneridagh +- Removed `date-fns` from dependencies, this was in the build because `Cypress` depended on it. After the `Cypress` upgrade it no longer depends on it. If your project still depends on it, add it as a dependency of your project. @sneridagh +- Removed all usage of `date-fns` from core. @sneridagh +- Rename `src/components/manage/Widgets/ColorPicker.jsx` component to `src/components/manage/Widgets/ColorPickerWidget.jsx` @sneridagh +- Remove the style wrapper around the `` component in Edit mode, moved to the main edit wrapper @sneridagh +- New `cloneDeepSchema` helper @sneridagh +- Action `listUsers`to be called with Object. Distinguish between search for id or search for fullname, email, username @ksuess +- Integrate volto-state add-on. @tiberiuichim @razvanmiu @eea +- Staticize Poppins font to be compliant with EU privacy. Import from GoogleFont is disabled in site.variables. @giuliaghisini +- Remove the `callout` button (the one with the megaphone icon) from the slate toolbar since it has the same styling as `blockquote`. If you need it anyway, you can bring it back in your addon. @sneridagh +- Using volto-slate Headline / Subheadline buttons strips all elements in the selection @tiberiuichim +- Use `Cypress` 10.3.0 (migrate from 9.x.x). Cypress 10 has some interesting goodies, being the native support of Apple Silicon Computers the main of it. See https://docs.voltocms.com/upgrade-guide/ for more information. @sneridagh +- The complete configuration registry is passed to the add-ons and the project configuration pipeline @sneridagh +- Refactor the component registry API in the configuration registry @sneridagh @tiberiuichim +- change password-reset url to be consistent with Plone configuration @erral +- Simplify over the existing Component Registry API. The `component` key has been flattened for simplification and now it's mapped directly to the `component` argument of `registerComponent`. @sneridagh +- This is an UI/UX breaking change. It changes the back button in folder contents from using a cross icon to using a back icon. The rationale behind is because the cross evoque "cancel" when what happens is a change of view. It's also consistent with both PastanagaUI and QuantaUI style guide. @robgietema +- Main workflow change menu changed from Pastanaga UI simplification to classic Plone implementation. @sneridagh +- Move Layout constants to `config.views.layoutViewsNamesMapping`. Complete the list. i18n the list. Improve Display component. @sneridagh +- `react-window` no longer a Volto dependency @sneridagh +- Upgrade to Razzle 4 @davisagli +- Jest downgraded from 27 to 26 @davisagli +- Sentry integration is now lazy-loaded. The `sentryOptions` key from the `settings` registry becomes a callable that passes resolved sentry libraries. @tiberiuichim +- Change history route name to `historyview` (same as classic) in order to allow content to have 'history' as `id` @danielamormocea +- The listing block icon has been improved to avoid confusions with the normal text list @sneridagh +- Remove the means to enable the StyleWrapper in favor of defining it through the block schema. @sneridagh +- Moved all sentry-related code from Volto to the `@plone-collective/volto-sentry` package. @tiberiuichim +- The listing block icon has been improved to avoid confusion with the normal text list. @sneridagh +- Restrict css selector for error message (volto-slate) #3838 @mamico +- Upgrade `husky` to latest version @sneridagh +- Enable the use of yarn 3 in the build by default @sneridagh +- The `ContentsBreadcrumbs` component now renders the whole language name of the language root folder (if any) instead of just the `id` (before: `de`, now: `Deutsch`) @sneridagh + +See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more information. + +### Feature + +- added default placeholder for videos to embed them more lightly @giuliaghisini +- Added new Block Style Wrapper. This implementation is marked as **experimental** during Volto 16 alpha period. The components, API and the styling are subject to change **without issuing a breaking change**. You can start using it in your projects and add-ons, but taking this into account. See documentation for more information. @sneridagh +- Add default widget views for all type of fields and improve the DefaultView @ionlizarazu +- added configurable identifier field for password reset in config.js. @giuliaghisini +- Add `expandToBackendURL` helper @sneridagh +- added 'show total results' option in Search block configuration. @giuliaghisini +- Added viewableInBrowserObjects setting to use in alternative to downloadableObjects, if you want to view file in browser intstead downloading. @giuliaghisini +- Disable already chosen criteria in querystring widget @kreafox +- Added X-Forwarded-\* headers to superagent requests. @mamico +- Updated Brazilian Portuguese translation @ericof +- Forward `HTTP Range` headers to the backend. @mamico +- Add default value to color picker, if `default` is present in the widget schema. @sneridagh +- Inject the classnames of the StyleWrapper into the main edit wrapper (it was wrapping directly the Edit component before). This way, the flexibility is bigger and you can act upon the whole edit container and artifacts (handlers, etc) @sneridagh +- Refactor image block: make it schema extensible @nileshgulia1 @sneridagh +- Add control panel via config.settings @ksuess https://github.com/plone/volto/issues/3426 +- Add noindex metadata tag @steffenri +- Adding Schema for Maps Block in Sidebar @iRohitSingh +- Add a Pluggable to the sharing page @JeffersonBledsoe #3372 +- Add listing variation schemaEnhancer to the search block schema @ionlizarazu +- Use the local blocksConfig for extensions, fallback to the config object one. This allows to override local blocks config in nested blocks (blocks in a block, eg. accordion, grid, row) @sneridagh +- Use type info instead of id type as icon title in the folder contents. @mamico +- Remove transifex configuration for Volto translations @erral +- Add missing support for inner `blocksConfig` in block extensions resolutions @sneridagh +- Add schema to video block sidebar @iRohitSingh @danielamormocea +- Add user group membership control panel @ksuess +- Action `listUsers`: Support search for fullname, email, username. @ksuess +- Added the `Undo controlpanel` to the controlpanels which can be used to undo transactions. @MdSahil-oss +- Send extra data coming from listing block schemaEnhancer from searchBlock to the listing variation @ionlizarazu +- support for many_users and many_groups flag in user controlpanel and group controlpanel @nileshgulia1 +- Show the content type of the content object you are adding/editing in the sidebar @robgietema +- Remove soft hyphens from the title tag @davisagli +- handle 'no connection' available error (408 error). @giuliaghisini +- Add support for OpenStreet Maps in Maps block @sneridagh +- Make `internalApiPath` client aware, since there are some corner cases when the client needs to know it to properly handle API server URLs @sneridagh +- Add initialPath support to ObjectBrowser widget @robgietema +- Added placeholder param to widget, to change default placeholder @giuliaghisini +- Add clear formatting button to slate @robgietema +- Support for getting `selectableTypes` and `maximumSelectionSize` from `widgetProps` @sneridagh +- Added placeholder param to widget, to change default placeholder @giuliaghisini +- Add a headline (`headline` field) to the listing block schema by default @sneridagh +- Add scroll into view setting to slate @robgietema +- Use absolute dates instead of "x hours ago" in History view @steffenri +- Complete eu translation @erral +- Complete es translation. @erral +- Added new components `Aliases` for aliases control in Volto. Alias management in both controlpanel and object view. @andreiggr @avoinea +- Added resetOnCancel functionality in Form component @MdSahil-oss +- volto-slate: introduce style-menu @nileshgulia1 +- Show result of the addon install/uninstall/upgrade actions @erral +- Working copy actions now render errors if they fail @pnicolli +- lazyloading of rrule lib. @giuliaghisini +- Complete eu translation. @erral +- Complete spanish translation @erral +- Added an option for users to set their own password through a confirmation email in the Add Users modal within the Users control panel. @JeffersonBledsoe #3710 +- Accept a `querystring` object in `apiExpanders` config object settings @sneridagh +- Add a dynamic user form based in @userschema endpoint @erral @nileshgulia1 +- Send missing variation data to the listing variation @ionlizarazu +- Logout action in personal tools points to the same pathname, now it logout in place, not in the root. @sneridagh +- Object browser: image search should only show images @reebalazs +- Updated spanish translation @macagua +- Add Dutch translation @spereverde +- Added link integrity potential breakage warning message when deleting a referenced page @danielamormocea +- Added new components & interfaces for content-rules `Rules` control in Volto. Rules management in both controlpanel and object view. @andreiggr +- Updated Spanish translation @macagua +- Introduce `TextLineEdit` component @sneridagh +- Add a popup tooltip for tokenized options in Select widget values @sneridagh +- Add `image-narrow` svg icon useful for align widget actions @ichim-david +- Use `View comments` and `Reply to item` permissions in `Comments` component. @razvanMiu +- Added portrait middleware adapter. @instification +- Allow dumping the addon dependency graph to a .dot file. Start Volto with `DEBUG_ADDONS_LOADER=true yarn start`, `addon-dependency-graph.dot` will be created in your project folder. @tiberiuichim +- Add clear button in search field of Folder content view @iFlameing +- consume site_actions from restapi @nileshgulia1 +- Updated Spanish translation @macagua +- Japanese translation updated @terapyon +- Improve the `AlignWidget`, add `narrow` fix default support @sneridagh +- Add support for loading core add-ons from the `packages` folder defined in Volto's `package.json` @sneridagh +- Implement the Upgrade Control Panel @ericof +- Allow addons to customize modules from the project root, via the `@root` namespace and folder @tiberiuichim +- Brazilian Portuguese translation updated @ericof +- Improvement of the `ContentsBreadcrumbs` component, add child `ContentsBreadcrumbsRootItem` and `ContentsBreadcrumbsHomeItem` for easy customization of these single elements in projects @sneridagh +- Add german translation for group membership panel. @ksuess +- Fix general german translations: Address user polite. Correct 'listing template' to 'listing variant'. Add missing translations. @ksuess +- Allow passing ariaHidden, id and style to an Icon's SVG @JeffersonBledsoe #3908 +- All Fields now understand the `default` prop as a fallback value in case their data value is missing. As a convenience, the `defaultValue` is also used as a fallback, but this shouldn't proliferate. @tiberiuichim +- There is an experimental setting to move the button for adding a new block to show below any selected block, instead of only on the left of empty text blocks. Set `config.experimental.addBlockButton.enabled = true` to enable it. @davisagli +- Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim +- Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh + +### Bugfix + +- Fix Search page visit crashes /contents view @dobri1408 +- Fix sidebar full size bottom opacity on edit page when sidebar is collapsed @ichim-david +- Fix toolbar bottom opacity on edit page when toolbar is collapsed @ichim-david +- Fix content view regression, height issue @danielamormocea +- Fixed secure cookie option. @giuliaghisini +- Changed addon order in addon controlpanel to mimic Classic UI @erral +- Fixed error when loading content in a language for which a Volto translation is not available. @davisagli +- Fix for clipped dropdown menus when the table has few or no records in Contents view @mihaislobozeanu +- fixed view video list from youtube in Video block. @giuliaghisini +- Fixed ICS URL in event view in seamless mode @sneridagh +- Fix `withStylingSchemaEnhancer` enhancer mechanism @sneridagh +- Add correct query parameters to the redirect @robgietema +- Fix RenderBlocks: path @ksuess +- Fix field id creation in dexterity control panel to have slugified id @erral +- Changed to get intl.locale always from state @ionlizarazu +- Fix regression, compound lang names (eg. `pt-BR`) no longer working @sneridagh +- fix TokenWidget choices when editing a recently created content. @giuliaghisini +- Fix color picker defaults implementation #2 @sneridagh +- Enable default color in `backgroundColor` default StyleWrapper field which wasn't sync with the default value setting @sneridagh +- Fix Block style wrapper: Cannot read properties of undefined (reading 'toString') @avoinea #3410 +- fix schema when content contains lock informations. @giuliaghisini +- Don't render junk when no facets are added to the search block @tiberiuichim +- Fix visibility of toolbar workflow dropdown for more states as fitting in .toolbar-content. @ksuess +- Fix the video block for anonymous user @iFlameing +- Use `cloneDeepSchema` helper for schema cloning operations, this fixes the error thrown in the use case of having JSX in the schema while cloning schema operations @sneridagh +- Fix CSS bundling in production mode to be consistent with the current policy in the client bundle. Right now the order of the CSS resources matches this chain: Loading of `import my-less.less` in add-ons (following the add-on order) -> Loading of the Semantic UI defaults -> Loading of the local theme (either project or add-on based). We are forcing now the bundling of all the CSS in one chunk, so it behaves the same than in dev mode (using the style-loader). @sneridagh +- Fixed the description field not being included in the navigation action/ reducer @JeffersonBledsoe #3454 +- Fixed a11y of Maps block (#3467) @iRohitSingh +- Prevent the `defaultView` to show anything if the content is not loaded yet. This fixes showing the non-blocks enabled view for a fraction of a second before showing the blocks-enabled one once the content is loaded. @sneridagh +- Fix typo in de locale @wolbernd +- Add some more messages to be able to translate them @erral +- Fix typo in de locale @wolbernd +- [generator] Improvements to the addon generator: Now it wires up the addon automatically for immediate local development @sneridagh +- complete eu translation @erral +- complete es translation @erral +- [generator] Add .editorconfig and .prettierignore to generated projects and addons. @ericof +- Make `crypto-random-string` a direct dep, fixing a hidden error since some updated dependency was requiring it directly but not anymore. @sneridagh +- Fix edge cases in Cypress flaky tests when the Edit component was loaded without loading the type schema. @sneridagh & @davisagli +- Fix edge cases in Cypress flaky tests when the Edit component was loaded for the wrong content path. @davisagli +- complete pt_BR translation @ericof +- Fix action `listUsers`. Provide default. @ksuess +- Provide the correct id to the blocks wrapped by StyleWrapper. @razvanMiu +- Remove console deprecation notice for 'host' property usage coming from Express @sneridagh +- Make Search page title translatable @erral +- Changed storeProtectLoadUtils location from src/storeProtectLoadUtils to src/middleware/storeProtectLoadUtils @MdSahil-oss +- Fix ArrayWidget choices when editing a recently created content item. @davisagli +- Fix content loading in `DefaultView` infinite loop if a listing block with no query is present. @sneridagh +- Fix login form redirect when it was loaded with a trailing slash @davisagli +- Better de translation for Site Setup @davisagli +- Fix overlapping for long words in Control Panel titles (added word-wrapping) @sneridagh +- Fix sitemap.xml.gz @robgietema +- Fix Image gallery listing block variation only gets 25 if no query is set @sneridagh +- Fix array widget translation @robgietema +- Fix: TTW DX Layout disables IBlocks behavior and with it all the indexers and transformers @avoinea +- Fix: Slate Editor: can not delete bullet point after adding it by typing "- " #3597 @dobri1408 +- Fix literal for the listing block edit mode message telling if the results are contained items (no query) or query results ones (query present) @sneridagh +- Fix grouping of the "users and groups" control panels (plone-users category) @sneridagh +- Improve `Display` and `Workflow` widgets in `More` menu. Fix alignments. @sneridagh +- Fixed searching in the sharing page not showing any results @JeffersonBledsoe #3579 +- Fix types menu on mobile for many types. Specific menuStyle for 'more' menu. @ksuess +- Fix types menu on desktop when menu overflows the viewport, adding scroll to it @sneridagh +- Fix "cannot have two html5 backends at the same time" error @davisagli +- Reset filter in folder contents when navigating @robgietema +- Fix bug showing incorrect history after a revert action @robgietema +- Fix and edge case, in case a `RelationList` has no default, on empty fields, after the object has been created, it saves an empty (None/null) value. Make sure that internally, if that's the case, it's an empty array always. @sneridagh +- Fix workflow and display select in toolbar in case that the option spans several lines @sneridagh +- Fix Press Enter in some blocks does not focus on the text block below #3647 @dobri1408 +- Add `matchAllRoutes` to AsyncConnect so that it matches all configured `asyncPropsExtenders` @tiberiuichim +- Fix acceptence test groups controlpanel @ksuess +- Fix the typo in change workflow status dialog in "de" @iRohitSingh +- Fix selection error when pressing backspace @robgietema +- Fix sidebarTab in Toc Block @iRohitSingh +- Fix virtualization (windowing) when displaying options with long titles for select widgets. (The virtualization happen when the number of options is greater than 25). Add dynamic height aware options using `react-virtualized`. @sneridagh +- Fix email validation to ensure all addresses are correctly validated @instification +- Fix number widget when the value is 0 @iRohitSingh +- Fix the typo in change workflow status dialog in "de" @iRohitSingh +- Show unauthorized message when accessing the diff view without permission @robgietema +- Fix i18n in title of Aliases control panel @sneridagh +- The styling schema is now applied before the block variations schema enhancers, to allow those enhancers a chance to tweak the styling schema @tiberiuichim +- Fix avatar URL in `PersonalTools`. Now works with the new `portrait` endpoint @sneridagh +- Fix `listing` block in SSR, now that it is fully variations aware and the configuration is passed to the SSR `querystring` action. @sneridagh +- Remove wrapping ul or ol when deselecting list style @robgietema +- Fix call to `@plone/scripts/i18n` (now a commonJS module) @sneridagh +- Concatenate multilingualRoutes and externalRoutes (if available) to defaultRoutes @erral #3653 +- Fixed the `description` field not appearing in control panel fieldsets @JeffersonBledsoe #3696 +- Fixed "more" always show root contents @MdSahil-oss #3365 +- Add missing `--noninteractive` in the `build` script in package.json @sneridagh +- Fix replace `` anchor element with the `UniversalLink` component in `DefaultTemplate.jsx` @Dnouv +- Extend Id widget validation rules to accept a dot "." @reebalazs +- Fix history page error for unauthenticated @reebalazs +- Fix unlock after changing the id and saving a page @reebalazs +- Group routes so React does not see them as a different Route and triggers a full remount. This is specially important in `Contents` @sneridagh +- Add default to `null` for `token` prop in `Navigation` component. This prevents the component to shoot an extra call when the logout happens @sneridagh +- Fix a double slash present in the `PersonalTools` component @sneridagh +- Fix UniversalLink storybook @tiberiuichim +- Fix logout to stay on the same page where the user was @reebalazs +- Change sentry chunk name to avoid ad blockers. Only load sentry if env vars exist @tiberiuichim +- SearchTags uses invalid vocabulary API @silviubogan +- Fix autocomplete widget with an empty search result @reebalazs +- Make sure that the store is reset on history reducer `PENDING` state @sneridagh +- Prefer views assigned explicitly with `layout` over views based on the `@type` @iRohitSingh +- Fix `schemaEnhancer` not being applied if nested `blocksConfig` is present @sneridagh +- Ensure the view component is always replaced after navigating to a different page. @davisagli +- Be more robust towards invalid block configuration @reebalazs +- Remove slate's builtin undo support, as it conflicts with Volto's undo manager. This fixes crashes when undoing in text blocks and slate's undo stack is empty and "crosses" into Volto's undo stack. This is a temporary workaround, ideally the two undo managers would be delimited so they each work together. @tiberiuichim +- Fix highlighting of selection when the Slate editor is not DOM-focused. @tiberiuichim +- Improve the algorithm that calculates the position of the Slate Toolbar @tiberiuichim +- The `_unwrapElement` of the volto-slate `ElementEditor` will return an updated range (selection) of the unwrapped element. @tiberiuichim +- Replace the main client entry point in `start-client.jsx` anonymous function for a named one. @sneridagh +- Fix `currentPath` option for `openObjectBrowser`. @iFlameing +- Fix updating the listing block when the variation is changed while editing @tiberiuichim +- fix(warning): StyleMenu dropdown item to use data-attr instead of custom @nileshgulia1 +- Added --canary flag in plone/install.sh. @MdSahil-oss +- Fix condition in `applySchemaDefaults` @tiberiuichim @sneridagh +- Load core add-ons configuration as any other add-on. @sneridagh +- Fix `FormValidation` error object, use field `id` instead of field `title` @sneridagh +- Revert #2828 PR change of the default `showSearchButton` Search block behavior (see [#3883](https://github.com/plone/volto/issues/3883)) @sneridagh +- Fix `package.json` `postinstall` in core @sneridagh +- Hide control panel settings that are not relevant to Volto @danalvrz +- Hide not relevant for Volto control panels from site setup, further refine not used inner settings for site control panel @sneridagh +- Fix ObjectWidget handling of `default` values coming from schemas. @tiberiuichim - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim - Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh - Cover an additional edge case for defaults @tiberiuichim +- Fix issue when using list markdown when list is already active (volto-slate) @robgietema +- Fix translation spelling of toggle @iFlameing +- Fix keyboard accessibility issue of Clear button in Folder content view @iFlameing + +### Internal + +- Improve Cypress integration, using Cypress official Github Action. Improve some flaky tests that showed up, and were known as problematic. Refactor and rename all the Github actions giving them meaningful names, and group them by type. Enable Cypress Dashboard for Volto. @sneridagh +- Stop using `xmlrpc` library for issuing the setup/teardown in core, use a `cy.request` instead. @sneridagh +- Added Cypress environment variables for adjusting the backend URL of commands @JeffersonBledsoe #3271 +- Reintroduce Plone 6 acceptance tests using the latests `plone.app.robotframework` 2.0.0a6 specific Volto fixture. @datakurre @ericof @sneridagh +- Upgrade all tests to use `plone.app.robotframework` 2.0.0a6 @sneridagh +- Upgrade Sentry to latest version because of [#3346](https://github.com/plone/volto/issues/3346) @sneridagh +- Update `Cypress` to version 9.6.1 @sneridagh +- Missing change from the last breaking change (Remove the style wrapper around the `` component in Edit mode, moved to the main edit wrapper). Now, really move it to the main edit wrapper @sneridagh +- Fix warning because missing key in `VersionOverview` component @sneridagh +- Mock all loadable libraries. @mamico +- Update json-schema including transitive dependencies @davisagli +- Update release-it @davisagli +- Deduplicate dependencies using yarn-deduplicate @davisagli +- Fix `defaultBlockType` entry in default config, set it to slate. @sneridagh +- Allow passing `allowedChildren` option to the BlockButton, to strip elements in headlines @tiberiuichim +- Upgrade to latest `@plone/scripts` @sneridagh +- Update browserlist definitions @sneridagh +- Fix propTypes for Pagination component @davisagli +- Test against Plone 5.2.9 and 6.0.0b1 @davisagli +- Use latest 1.6.0 `@plone/scripts` @sneridagh +- Add classname of variation in edit mode @iFlameing +- Use component registry for default image, fallback to the local import @sneridagh +- Remove Razzle as direct dependency from @plone/scripts @sneridagh +- Fix storybook build for Razzle 4 @sneridagh +- Update `@plone/scripts` to 2.1.1 @sneridagh +- Run yarn deduplicate on dependencies. @davisagli +- Comment out flaky test for now regarding many users/groups @sneridagh +- Add reverse proxy conf with `traefik` to demo compose file @sneridagh +- More disable flaky test regarding many users/groups @sneridagh +- Remove no longer present option in cypress github action, by default, headless is true @sneridagh +- Add proper webserver with reverse proxy with seamless mode @sneridagh +- Update to Plone 6 beta3 @sneridagh +- Upgrade Cypress to latest @sneridagh +- Upgrade dependency rrule (optional dependency luxon removed) @ksuess +- Set `.nvmrc` to not use `lts/*` but a specific one `lts/gallium` +- Update to @plone/scripts 2.1.2 @sneridagh +- Remove all the useless security bits from blocks configuration definitions @sneridagh +- Add translation for `pending` state @iFlameing +- Add `composeSchema`, a helper to compose multiple schemaEnhancers @tiberiuichim +- Upgrade to `plone.voltoa14` @sneridagh +- Upgrade dependencies to latest released slate libraries. Make sure to pass down `ref` to rendered slate elements, as ref is now a function @tiberiuichim +- Add `editableProps` prop to the `SlateEditor` component, to pass down props to the base Slate `Editable` component. @tiberiuichim +- Clean, re-enable block-slate-format-link Cypress tests @tiberiuichim +- Rewrite some anonymous functions as named functions, to remove warning about Hot Reloading. @tiberiuichim +- Add translation for objectlist `Add` text @iFlameing +- Add translations for facet widget value @iFlameing +- Ignore `.tool-versions` file +- Minor updates to dependencies +- Update Cypress 11 @sneridagh +- Update to Plone 6 RC1 @sneridagh + +### Documentation + +- Move Cypress documentation from `README.md` to the docs. Improve the docs with the new `Makefile` commands. +- Improve English grammar and syntax in backend docs. @stevepiercy +- Fix JSX syntax highlighting. Remove duplicate heading. @stevepiercy +- fix make task `docs-linkcheckbroken` if grep has exit code 1 (no lines found) +- Updated simple.md @MdSahil-oss +- Fix indentation in nginx configuration in simple.md @stevepiercy +- Remove sphinx_sitemap configuration because Volto's docs are now imported into the main docs, making this setting unnecessary. @stevepiercy +- Set the ogp_site_url to main docs, instead of training. @stevepiercy +- `aria-*` attributes are now parsed correctly by jsx-lexer 2.0. @stevepiercy +- volto-slate documentation @nileshgulia1 +- Fix redirect on YouTube, broken link after merge and deleted branch. @stevepiercy +- Add upgrade guide documentation for dealing with `volto-slate` upgrades for Volto 16 alpha 15 onwards. @sneridagh +- Minor clean up of volto-slate upgrade guide. @stevepiercy +- Rework documentation on how to write a Slate plugin @ksuess +- Documentation of the new component registry API @sneridagh +- Fix copy / paste text in list @robgietema +- Make links relative to `_static` so that `plone/documentation` can pull them in, and fix broken link. @stevepiercy +- Align `html_static_path` with `plone/documentation` and image path so that images render when docs build in both repos. @stevepiercy +- Undo html_static_path configuration in `plone/documentation`, and restore image and its referenced path in `plone/volto`. @stevepiercy +- Clean up "design principles" and "contributing" +- Bring back "Guidelines for Contributing" +- Fix Sphinx warning `WARNING: glossary terms must not be separated by empty lines` by closing unclosed glossary directive's triple backticks. @stevepiercy +- Fix broken links to nvm releases. @stevepiercy +- Ignore redirect that requires login to GitHub. @stevepiercy +- Added controls for the `actions` property of the `AlignWidget` storybook @JeffersonBledsoe #3671 +- Generic Setup -> `GenericSetup`. @stevepiercy +- Upgrade to Plone 6 beta 2 @sneridagh +- Flip testing matrix for acceptance tests, make Plone 6 principal subject, Plone 5 as secondary @sneridagh +- Update README with latest versions, point to Plone 6 as recommended default @sneridagh +- Trigger a new deploy core Plone documentation when Volto documentation is updated @esteele +- Update supported Python versions. @stevepiercy +- Add NodeJS 18 (LTS) usage notice @sneridagh +- Fix Netlify build @sneridagh +- Fix grammar in Theming Strategy. Fixes #954. @stevepiercy +- Fix wording in About Semantic UI. Fixes #953. @stevepiercy +- Add missing pieces of the upgrade to use yarn 3 for projects @sneridagh +- Complete docs about the yarn 3 upgrade @sneridagh +- Add additional components to storybook @danalvrz +- Add `@plone/scripts` as a mandatory devDependency for projects to the upgrade guide @sneridagh +- Document `Sentry` integration move from Volto core to add-on `@plone-collective/volto-sentry` in configuration, upgrade and deployment. @ksuess +- Remove `sentryOptions` from settings reference. Clean up `deploying/sentry.md`. @stevepiercy +- Tidy up `upgrade-guide/index.md`. @stevepiercy +- Fix some MyST syntax and English grammar. @stevepiercy + +## 16.0.0-rc.3 (2022-11-22) + +### Bugfix + - Fix keyboard accessibility issue of Clear button in Folder content view @iFlameing - Fix issue when using list markdown when list is already active (volto-slate) @robgietema - Fix translation spelling of toggle @iFlameing +### Documentation + +- Document experimental features @davisagli + +## 16.0.0-rc.2 (2022-11-20) + +### Bugfix + +- Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim +- Cover an additional edge case for defaults @tiberiuichim + ### Internal - Update to Plone 6 RC1 @sneridagh From f046c60be79b9112d955569deb8287ae2a774d8e Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Tue, 22 Nov 2022 19:13:26 +0100 Subject: [PATCH 070/326] Sync version to 16.0.0 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4fd64196c..92d01274dd 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "16.0.0-rc.1", + "version": "16.0.0", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 65a81b59fa4fb9be014003989a59a1b473b39632 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Wed, 23 Nov 2022 11:40:15 +0200 Subject: [PATCH 071/326] Fix babel for jest (#3959) --- CHANGELOG.md | 2 ++ package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 453c69e9e9..07f0861114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ ### Bugfix +- Fix jest moduleNameMapper for `@plone/volto/babel` @tiberiuichim + ### Internal ### Documentation diff --git a/package.json b/package.json index 92d01274dd..d30f38a2e4 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ }, "moduleNameMapper": { "@plone/volto/package.json": "/package.json", + "@plone/volto/babel": "/babel.js", "@plone/volto/(.*)$": "/src/$1", "@plone/volto-slate": "/packages/volto-slate/src", "~/config": "/src/config", From d39951530f07e369d0ba2771bab29b4c1f2070c6 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Wed, 23 Nov 2022 13:08:59 +0200 Subject: [PATCH 072/326] Revert "Fix babel for jest" (#3963) --- CHANGELOG.md | 2 -- package.json | 1 - 2 files changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f0861114..453c69e9e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,6 @@ ### Bugfix -- Fix jest moduleNameMapper for `@plone/volto/babel` @tiberiuichim - ### Internal ### Documentation diff --git a/package.json b/package.json index d30f38a2e4..92d01274dd 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ }, "moduleNameMapper": { "@plone/volto/package.json": "/package.json", - "@plone/volto/babel": "/babel.js", "@plone/volto/(.*)$": "/src/$1", "@plone/volto-slate": "/packages/volto-slate/src", "~/config": "/src/config", From 24c04c7fde707f2dbb32fa8722e8f725874a81b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 23 Nov 2022 12:39:23 +0100 Subject: [PATCH 073/326] Enable proper defaults in `BlocksDataForm` in stock core blocks (#3964) --- CHANGELOG.md | 2 ++ src/components/manage/Blocks/HeroImageLeft/Data.jsx | 3 ++- src/components/manage/Blocks/Image/ImageSidebar.jsx | 1 + src/components/manage/Blocks/Listing/ListingData.jsx | 1 + src/components/manage/Blocks/Maps/MapsSidebar.jsx | 1 + src/components/manage/Blocks/Search/SearchBlockEdit.jsx | 1 + src/components/manage/Blocks/ToC/Edit.jsx | 1 + src/components/manage/Blocks/Video/VideoSidebar.jsx | 2 +- 8 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 453c69e9e9..5349b725f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ ### Bugfix +- Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh + ### Internal ### Documentation diff --git a/src/components/manage/Blocks/HeroImageLeft/Data.jsx b/src/components/manage/Blocks/HeroImageLeft/Data.jsx index 8f7633e329..074692add7 100644 --- a/src/components/manage/Blocks/HeroImageLeft/Data.jsx +++ b/src/components/manage/Blocks/HeroImageLeft/Data.jsx @@ -9,7 +9,6 @@ const HeroImageLeftBlockData = (props) => { const schema = schemaHero({ ...props, intl }); return ( { @@ -18,7 +17,9 @@ const HeroImageLeftBlockData = (props) => { [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={data} + block={block} /> ); }; diff --git a/src/components/manage/Blocks/Image/ImageSidebar.jsx b/src/components/manage/Blocks/Image/ImageSidebar.jsx index be81b3ebb7..34a1546f80 100644 --- a/src/components/manage/Blocks/Image/ImageSidebar.jsx +++ b/src/components/manage/Blocks/Image/ImageSidebar.jsx @@ -52,6 +52,7 @@ const ImageSidebar = (props) => { [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={data} block={block} /> diff --git a/src/components/manage/Blocks/Listing/ListingData.jsx b/src/components/manage/Blocks/Listing/ListingData.jsx index 170d989a07..335db851fe 100644 --- a/src/components/manage/Blocks/Listing/ListingData.jsx +++ b/src/components/manage/Blocks/Listing/ListingData.jsx @@ -19,6 +19,7 @@ const ListingData = (props) => { [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={data} block={block} /> diff --git a/src/components/manage/Blocks/Maps/MapsSidebar.jsx b/src/components/manage/Blocks/Maps/MapsSidebar.jsx index 68977cf40c..708bd80d63 100644 --- a/src/components/manage/Blocks/Maps/MapsSidebar.jsx +++ b/src/components/manage/Blocks/Maps/MapsSidebar.jsx @@ -39,6 +39,7 @@ const MapsSidebar = (props) => { [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={data} block={block} /> diff --git a/src/components/manage/Blocks/Search/SearchBlockEdit.jsx b/src/components/manage/Blocks/Search/SearchBlockEdit.jsx index 436010591b..3f5b57bf91 100644 --- a/src/components/manage/Blocks/Search/SearchBlockEdit.jsx +++ b/src/components/manage/Blocks/Search/SearchBlockEdit.jsx @@ -80,6 +80,7 @@ const SearchBlockEdit = (props) => { [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={data} /> diff --git a/src/components/manage/Blocks/ToC/Edit.jsx b/src/components/manage/Blocks/ToC/Edit.jsx index 5b91dab811..8932e9919b 100644 --- a/src/components/manage/Blocks/ToC/Edit.jsx +++ b/src/components/manage/Blocks/ToC/Edit.jsx @@ -24,6 +24,7 @@ class Edit extends Component { [id]: value, }); }} + onChangeBlock={this.props.onChangeBlock} formData={this.props.data} /> diff --git a/src/components/manage/Blocks/Video/VideoSidebar.jsx b/src/components/manage/Blocks/Video/VideoSidebar.jsx index 9a0b21de16..a80a31ed79 100644 --- a/src/components/manage/Blocks/Video/VideoSidebar.jsx +++ b/src/components/manage/Blocks/Video/VideoSidebar.jsx @@ -39,8 +39,8 @@ const VideoSidebar = (props) => { [id]: value, }); }} + onChangeBlock={onChangeBlock} formData={data} - fieldIndex={data.index} block={block} /> )} From ba9543b92e2f25ff36f7079dcfdab9de0ba85702 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Wed, 23 Nov 2022 14:14:50 +0200 Subject: [PATCH 074/326] Fix addons loader test (#3961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 2 ++ __tests__/create-addons-loader.test.js | 2 +- package.json | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5349b725f4..a1e38c72b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ ### Bugfix +- Fix jest moduleNameMapper for `@plone/volto/babel` @tiberiuichim +- Fix addons loader test @tiberiuichim - Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh ### Internal diff --git a/__tests__/create-addons-loader.test.js b/__tests__/create-addons-loader.test.js index 4500ec887e..c98f61d107 100644 --- a/__tests__/create-addons-loader.test.js +++ b/__tests__/create-addons-loader.test.js @@ -133,7 +133,7 @@ function transpile(fpath) { const code = fs.readFileSync(fpath, 'utf-8'); // console.log('original code', code); const output = transform(code, { - root: '../', + root: '.', plugins: ['@babel/plugin-transform-modules-commonjs'], }); fs.writeFileSync(fpath, output.code); diff --git a/package.json b/package.json index 92d01274dd..d30f38a2e4 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ }, "moduleNameMapper": { "@plone/volto/package.json": "/package.json", + "@plone/volto/babel": "/babel.js", "@plone/volto/(.*)$": "/src/$1", "@plone/volto-slate": "/packages/volto-slate/src", "~/config": "/src/config", From 7b142a4f9c8628fb1d30347e9f4f7de7e3b1754f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Re=C3=A9?= Date: Wed, 23 Nov 2022 13:15:03 +0100 Subject: [PATCH 075/326] Fix user search by full name in users control panel (#3962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 5 +++-- .../manage/Controlpanels/Users/UsersControlpanel.jsx | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e38c72b0..abf0a44b9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Fix jest moduleNameMapper for `@plone/volto/babel` @tiberiuichim - Fix addons loader test @tiberiuichim - Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh +- Fix user search by full name in users control panel @reebalazs ### Internal @@ -276,7 +277,7 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa - Fix image tag for Plone 5.2.x, use 5.2.9 for now @sneridagh - Cover an additional edge case for defaults @tiberiuichim - Fix issue when using list markdown when list is already active (volto-slate) @robgietema -- Fix translation spelling of toggle @iFlameing +- Fix translation spelling of toggle @iFlameing - Fix keyboard accessibility issue of Clear button in Folder content view @iFlameing ### Internal @@ -384,7 +385,7 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa - Fix keyboard accessibility issue of Clear button in Folder content view @iFlameing - Fix issue when using list markdown when list is already active (volto-slate) @robgietema -- Fix translation spelling of toggle @iFlameing +- Fix translation spelling of toggle @iFlameing ### Documentation diff --git a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx index d7ca9226c2..17a983b673 100644 --- a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +++ b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx @@ -140,7 +140,7 @@ class UsersControlpanel extends Component { (this.props.createRequest.loading && nextProps.createRequest.loaded) ) { this.props.listUsers({ - query: this.state.search, + search: this.state.search, }); } if (this.props.createRequest.loading && nextProps.createRequest.loaded) { @@ -172,7 +172,7 @@ class UsersControlpanel extends Component { onSearch(event) { event.preventDefault(); this.props.listUsers({ - query: this.state.search, + search: this.state.search, }); } From 68019b7e6e1a7581438194a0735fb6f00985fdb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Thu, 24 Nov 2022 17:19:21 +0100 Subject: [PATCH 076/326] Improve and fix `@plone/scripts` `@plone/generate-volto` to be fully compliant with the latest best practices (#3975) --- packages/generator-volto/CHANGELOG.md | 15 +++- .../addon/templates/cypress/support/index.js | 7 +- .../cypress/support/reset-fixture.js | 75 ++++++++++--------- .../generators/app/templates/Makefile | 55 +++++++++++--- .../generators/app/templates/README.md | 16 ++++ .../app/templates/cypress/support/index.js | 7 +- .../cypress/support/reset-fixture.js | 75 ++++++++++--------- .../generators/app/templates/package.json.tpl | 15 +--- packages/generator-volto/package.json | 2 +- packages/scripts/CHANGELOG.md | 33 +++++++- packages/scripts/addon/generators.js | 62 ++++++++++----- packages/scripts/addon/utils.js | 9 +-- packages/scripts/package.json | 2 +- 13 files changed, 240 insertions(+), 133 deletions(-) diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index dbfcf0d64b..68a9bc062a 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 6.0.1 (unreleased) +## 6.1.2 (unreleased) ### Breaking @@ -10,6 +10,19 @@ ### Internal +## 6.1.1 (2022-11-24) + +### Bugfix + +- Updated reset fixture scripts for generator @sneridagh + +## 6.1.0 (2022-11-24) + +### Feature + +- Refactor the `package.json` scripts, move all Cypress related to `Makefile` commands, remove the scripts that are obsolete as well. @sneridagh +- Update the default Plone Versions from the convenience docker images in `Makefile` @sneridagh + ## 6.0.0 (2022-11-22) ### Internal diff --git a/packages/generator-volto/generators/addon/templates/cypress/support/index.js b/packages/generator-volto/generators/addon/templates/cypress/support/index.js index 06b1c0cad9..48912f7f19 100644 --- a/packages/generator-volto/generators/addon/templates/cypress/support/index.js +++ b/packages/generator-volto/generators/addon/templates/cypress/support/index.js @@ -1,13 +1,14 @@ +import 'cypress-axe'; import 'cypress-file-upload'; import './commands'; -import 'cypress-axe'; +import { setup, teardown } from './reset-fixture'; beforeEach(function () { cy.log('Setting up API fixture'); - cy.exec('yarn cy:test:fixture:setup'); + setup(); }); afterEach(function () { cy.log('Tearing down API fixture'); - cy.exec('yarn cy:test:fixture:teardown'); + teardown(); }); diff --git a/packages/generator-volto/generators/addon/templates/cypress/support/reset-fixture.js b/packages/generator-volto/generators/addon/templates/cypress/support/reset-fixture.js index f142eb6be9..a9a5fad79c 100644 --- a/packages/generator-volto/generators/addon/templates/cypress/support/reset-fixture.js +++ b/packages/generator-volto/generators/addon/templates/cypress/support/reset-fixture.js @@ -1,44 +1,45 @@ -const xmlrpc = require('xmlrpc'); -const command = process.argv[2]; - -// create a client -const client = xmlrpc.createClient({ - host: process.env.CYPRESS_BACKEND_HOST || 'localhost', - port: 55001, - path: '/plone/RobotRemote', -}); - function setup() { - // Setup site - client.methodCall( - 'run_keyword', - [ - 'remote_zodb_setup', - ['plone.app.robotframework.testing.PLONE_ROBOT_TESTING'], - ], - () => {}, - ); + const api_url = Cypress.env('API_PATH') || 'http://localhost:55001/plone'; + cy.request({ + method: 'POST', + url: `${api_url}/RobotRemote`, + headers: { Accept: 'text/xml', 'content-type': 'text/xml' }, + body: + 'run_keywordremote_zodb_setupplone.app.robotframework.testing.PLONE_ROBOT_TESTING', + }).then(() => cy.log('Setting up API fixture')); } function teardown() { - // Tearing down - client.methodCall( - 'run_keyword', - [ - 'remote_zodb_teardown', - ['plone.app.robotframework.testing.PLONE_ROBOT_TESTING'], - ], - () => {}, - ); + const api_url = Cypress.env('API_PATH') || 'http://localhost:55001/plone'; + cy.request({ + method: 'POST', + url: `${api_url}/RobotRemote`, + headers: { Accept: 'text/xml', 'content-type': 'text/xml' }, + body: + 'run_keywordremote_zodb_teardownplone.app.robotframework.testing.PLONE_ROBOT_TESTING', + }).then(() => cy.log('Tearing down API fixture')); } -switch (command) { - case 'setup': - setup(); - break; - case 'teardown': - teardown(); - break; - default: - setup(); +function main() { + const command = process.argv[2]; + switch (command) { + case 'setup': + setup(); + break; + case 'teardown': + teardown(); + break; + default: + setup(); + } } + +// This is the equivalent of `if __name__ == '__main__'` in Python :) +if (require.main === module) { + main(); +} + +module.exports = { + setup, + teardown, +}; diff --git a/packages/generator-volto/generators/app/templates/Makefile b/packages/generator-volto/generators/app/templates/Makefile index fe9f4aff61..9b7b434aba 100644 --- a/packages/generator-volto/generators/app/templates/Makefile +++ b/packages/generator-volto/generators/app/templates/Makefile @@ -1,5 +1,3 @@ -# Yeoman Volto App development - ### Defensive settings for make: # https://tech.davis-hansson.com/p/make/ SHELL:=bash @@ -10,11 +8,16 @@ SHELL:=bash MAKEFLAGS+=--warn-undefined-variables MAKEFLAGS+=--no-builtin-rules -# Update the versions depending on your project requirements | Last Updated 2022-07-24 -DOCKER_IMAGE=plone/plone-backend:6.0.0b1 -KGS=plone.volto==4.0.0a7 +# Update the versions depending on your project requirements | Last Updated 2022-11-24 +DOCKER_IMAGE=plone/plone-backend:6.0.0rc1 +KGS= +TESTING_ADDONS=plone.app.robotframework==2.0.0b2 plone.app.testing==7.0.0b2 NODEBIN = ./node_modules/.bin +# Plone 5 legacy +DOCKER_IMAGE5=plone/plone-backend:5.2.9 +KGS5=plone.restapi==8.32.2 plone.volto==4.0.0 plone.rest==2.0.0 + # Project settings DIR=$(shell basename $$(pwd)) @@ -37,13 +40,6 @@ all: project help: ## Show this help. @echo -e "$$(grep -hE '^\S+:.*##' $(MAKEFILE_LIST) | sed -e 's/:.*##\s*/:/' -e 's/^\(.\+\):\(.*\)/\\x1b[36m\1\\x1b[m:\2/' | column -c2 -t -s :)" -.PHONY: start-test-backend -start-test-backend: ## Start Test Plone Backend - @echo "$(GREEN)==> Start Test Plone Backend$(RESET)" - docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS) plone.app.robotframework==2.0.0a3 plone.app.testing==7.0.0a2 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 - ## KGS in case you need a Plone 5.2 series (comment/remove above line): - # docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS) plone.app.robotframework==2.0.0a3 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: start-backend-docker start-backend-docker: ## Starts a Docker-based backend @echo "$(GREEN)==> Start Docker-based Plone Backend$(RESET)" @@ -71,3 +67,38 @@ omelette: ## Creates the omelette folder that contains a link to the installed v .PHONY: patches patches: /bin/bash patches/patchit.sh > /dev/null 2>&1 ||true + +.PHONY: start-test-acceptance-server start-test-backend +start-test-acceptance-server start-test-backend : ## Start Test Plone Backend + @echo "$(GREEN)==> Start Test Plone Backend$(RESET)" + docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS) $(TESTING_ADDONS)' -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.VOLTO_ROBOT_TESTING + ## KGS in case you need a Plone 5.2 series (comment/remove above line): + # docker run -i --rm -e ZSERVER_HOST=0.0.0.0 -e ZSERVER_PORT=55001 -p 55001:55001 -e ADDONS='$(KGS5) $(TESTING_ADDONS)' -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_IMAGE5) ./bin/robot-server plone.app.robotframework.testing.VOLTO_ROBOT_TESTING + +.PHONY: start-test-acceptance-frontend +start-test-acceptance-frontend: ## Start the Acceptance Frontend Fixture + RAZZLE_API_PATH=http://localhost:55001/plone yarn build && yarn start:prod + +.PHONY: test-acceptance +test-acceptance: ## Start Core Cypress Acceptance Tests + $(NODEBIN)/cypress open + +.PHONY: test-acceptance-headless +test-acceptance-headless: ## Start Core Cypress Acceptance Tests in headless mode + $(NODEBIN)/cypress run + +.PHONY: full-test-acceptance +full-test-acceptance: ## Runs Core Full Acceptance Testing in headless mode + $(NODEBIN)/start-test "make start-test-acceptance-server" http-get://localhost:55001/plone "make start-test-acceptance-frontend" http://localhost:3000 "make test-acceptance-headless" + +.PHONY: test-acceptance +test-acceptance-addon: ## Start Core Cypress Acceptance Tests for an addon + $(NODEBIN)/cypress open -P $(ADDONPATH) + +.PHONY: test-acceptance-headless +test-acceptance-addon-headless: ## Start Core Cypress Acceptance Tests for an addon in headless mode + $(NODEBIN)/cypress run -P $(ADDONPATH) + +.PHONY: full-test-acceptance-addon +full-test-acceptance-addon: ## Runs Core Full Acceptance Testing for an addon in headless mode + $(NODEBIN)/start-test "make start-test-acceptance-server" http-get://localhost:55001/plone "make start-test-acceptance-frontend" http://localhost:3000 "make test-acceptance-addon-headless" diff --git a/packages/generator-volto/generators/app/templates/README.md b/packages/generator-volto/generators/app/templates/README.md index 5776d0375f..1410a4d24e 100644 --- a/packages/generator-volto/generators/app/templates/README.md +++ b/packages/generator-volto/generators/app/templates/README.md @@ -6,6 +6,10 @@ A training on how to create your own website using Volto is available as part of Below is a list of commands you will probably find useful. +### `make install` + +Installs and checkouts the `mrs-developer` directives (`make develop`), creates a shortcut to the Volto source code (`omelette` folder), then triggers the install of the frontend environment. + ### `yarn start` Runs the project in development mode. @@ -54,3 +58,15 @@ In case you don't want (or can't) install mrs-developer globally, you can instal ```bash yarn add -W mrs-developer ``` + +## Acceptance tests + +In order to run localy (while developing) the project acceptance tests (Cypress), there are some `Makefile` commands in place (in the repo root). Run them in order: + +`start-test-acceptance-server`: Start server fixture in docker (previous build required) + +`start-test-acceptance-frontend`: Start the Core Acceptance Frontend Fixture in dev mode + +`test-acceptance`: Start Core Cypress Acceptance Tests in dev mode + +`full-test-acceptance`: Start the whole suite (backend + frontend + headless tests) Cypress Acceptance Tests in headless (CI) mode diff --git a/packages/generator-volto/generators/app/templates/cypress/support/index.js b/packages/generator-volto/generators/app/templates/cypress/support/index.js index 06b1c0cad9..48912f7f19 100644 --- a/packages/generator-volto/generators/app/templates/cypress/support/index.js +++ b/packages/generator-volto/generators/app/templates/cypress/support/index.js @@ -1,13 +1,14 @@ +import 'cypress-axe'; import 'cypress-file-upload'; import './commands'; -import 'cypress-axe'; +import { setup, teardown } from './reset-fixture'; beforeEach(function () { cy.log('Setting up API fixture'); - cy.exec('yarn cy:test:fixture:setup'); + setup(); }); afterEach(function () { cy.log('Tearing down API fixture'); - cy.exec('yarn cy:test:fixture:teardown'); + teardown(); }); diff --git a/packages/generator-volto/generators/app/templates/cypress/support/reset-fixture.js b/packages/generator-volto/generators/app/templates/cypress/support/reset-fixture.js index f142eb6be9..a9a5fad79c 100644 --- a/packages/generator-volto/generators/app/templates/cypress/support/reset-fixture.js +++ b/packages/generator-volto/generators/app/templates/cypress/support/reset-fixture.js @@ -1,44 +1,45 @@ -const xmlrpc = require('xmlrpc'); -const command = process.argv[2]; - -// create a client -const client = xmlrpc.createClient({ - host: process.env.CYPRESS_BACKEND_HOST || 'localhost', - port: 55001, - path: '/plone/RobotRemote', -}); - function setup() { - // Setup site - client.methodCall( - 'run_keyword', - [ - 'remote_zodb_setup', - ['plone.app.robotframework.testing.PLONE_ROBOT_TESTING'], - ], - () => {}, - ); + const api_url = Cypress.env('API_PATH') || 'http://localhost:55001/plone'; + cy.request({ + method: 'POST', + url: `${api_url}/RobotRemote`, + headers: { Accept: 'text/xml', 'content-type': 'text/xml' }, + body: + 'run_keywordremote_zodb_setupplone.app.robotframework.testing.PLONE_ROBOT_TESTING', + }).then(() => cy.log('Setting up API fixture')); } function teardown() { - // Tearing down - client.methodCall( - 'run_keyword', - [ - 'remote_zodb_teardown', - ['plone.app.robotframework.testing.PLONE_ROBOT_TESTING'], - ], - () => {}, - ); + const api_url = Cypress.env('API_PATH') || 'http://localhost:55001/plone'; + cy.request({ + method: 'POST', + url: `${api_url}/RobotRemote`, + headers: { Accept: 'text/xml', 'content-type': 'text/xml' }, + body: + 'run_keywordremote_zodb_teardownplone.app.robotframework.testing.PLONE_ROBOT_TESTING', + }).then(() => cy.log('Tearing down API fixture')); } -switch (command) { - case 'setup': - setup(); - break; - case 'teardown': - teardown(); - break; - default: - setup(); +function main() { + const command = process.argv[2]; + switch (command) { + case 'setup': + setup(); + break; + case 'teardown': + teardown(); + break; + default: + setup(); + } } + +// This is the equivalent of `if __name__ == '__main__'` in Python :) +if (require.main === module) { + main(); +} + +module.exports = { + setup, + teardown, +}; diff --git a/packages/generator-volto/generators/app/templates/package.json.tpl b/packages/generator-volto/generators/app/templates/package.json.tpl index ad1e31a9c6..58d155560d 100644 --- a/packages/generator-volto/generators/app/templates/package.json.tpl +++ b/packages/generator-volto/generators/app/templates/package.json.tpl @@ -17,19 +17,10 @@ "stylelint:overrides": "stylelint 'theme/**/*.overrides' 'src/**/*.overrides'", "stylelint:fix": "yarn stylelint --fix && yarn stylelint:overrides --fix", "test": "razzle test --passWithNoTests", - "cypress:run": "NODE_ENV=test cypress run", - "cypress:open": "NODE_ENV=test cypress open", - "cypress:start-frontend": "RAZZLE_API_PATH=http://localhost:55001/plone yarn start", - "cypress:test-acceptance-server": "make test-acceptance-server", - "cy:test:fixture:setup": "node cypress/support/reset-fixture.js", - "cy:test:fixture:teardown": "node cypress/support/reset-fixture.js teardown", - "ci:start-backend": "make start-test-backend", - "ci:start-frontend": "RAZZLE_API_PATH=http://localhost:55001/plone yarn build && start-test start:prod http-get://localhost:3000 cypress:run", - "ci:cypress:run": "start-test ci:start-backend http-get://localhost:55001/plone ci:start-frontend", + "cypress:open": "make test-acceptance", + "cypress:run": "test-acceptance-headless", "start:prod": "NODE_ENV=production node build/server.js", "i18n": "rm -rf build/messages && NODE_ENV=production i18n", - "develop:npx": "npx -p mrs-developer missdev --config=jsconfig.json --output=addons --fetch-https", - "develop": "missdev --config=jsconfig.json --output=addons --fetch-https", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook" }, @@ -143,7 +134,7 @@ "mrs-developer": "*", "postcss": "8.4.13", "prettier": "2.0.5", - "@plone/scripts": "^2.1.2", + "@plone/scripts": "^2.1.5", "@storybook/addon-actions": "^6.3.0", "@storybook/addon-controls": "6.3.0", "@storybook/addon-essentials": "^6.3.0", diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index b0bd3cda2a..70e476de2b 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "6.0.0", + "version": "6.1.1", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 7fbf3c698d..8c7726ef25 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 2.1.3 (unreleased) +## 2.2.2 (unreleased) ### Breaking @@ -10,6 +10,37 @@ ### Internal +## 2.2.1 (2022-11-24) + +### Bugfix + +- Include `cypress` folder and `cypress.config.js` in the local clone command @sneridagh +- Fix the local clone command `execSync` @sneridagh + +## 2.2.0 (2022-11-24) + +### Feature + +- Match the new layout for the Volto project generator for Cypress tests @sneridagh + +## 2.1.5 (2022-11-24) + +### Bugfix + +- Remove `isCanary` amendment to differentiate 15/16 way of calling the test script @sneridagh + +## 2.1.4 (2022-11-24) + +### Bugfix + +- Disable immutable installs in local package once created, so CI does not complain. We REALLY want to install something! @sneridagh + +## 2.1.3 (2022-11-24) + +### Bugfix + +- Improve `execSync` call in addon script @sneridagh + ## 2.1.2 (2022-10-26) ### Internal diff --git a/packages/scripts/addon/generators.js b/packages/scripts/addon/generators.js index 5a3074c00e..ebfd69133e 100644 --- a/packages/scripts/addon/generators.js +++ b/packages/scripts/addon/generators.js @@ -79,16 +79,20 @@ export async function runGitGenerator({ output: 'addons', }); - execSync(`cd ${destination} && yarn`, (error, stdout, stderr) => { - if (error) { - console.log(`error: ${error.message}`); - return; - } - if (stderr) { - console.log(`stderr: ${stderr}`); - return; - } - }); + execSync( + `cd ${destination} && yarn config set enableImmutableInstalls false && yarn install`, + { stdio: 'inherit' }, + (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`); + return; + } + if (stderr) { + console.log(`stderr: ${stderr}`); + return; + } + }, + ); console.log( chalk.green( @@ -155,6 +159,20 @@ export async function runLocalGenerator({ overwrite: false, }, ); + fse.copySync( + `${source}/cypress`, + `${destination}/src/addons/${name}/cypress`, + { + overwrite: false, + }, + ); + fse.copySync( + `${source}/cypress.config.js`, + `${destination}/src/addons/${name}/cypress.config.js`, + { + overwrite: false, + }, + ); } catch (err) { console.error(err); } @@ -173,16 +191,20 @@ export async function runLocalGenerator({ output: 'addons', }); - execSync(`cd ${destination} && yarn`, (error, stdout, stderr) => { - if (error) { - console.log(`error: ${error.message}`); - return; - } - if (stderr) { - console.log(`stderr: ${stderr}`); - return; - } - }); + execSync( + `cd ${destination} && yarn config set enableImmutableInstalls false && yarn install`, + { stdio: 'inherit' }, + (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`); + return; + } + if (stderr) { + console.log(`stderr: ${stderr}`); + return; + } + }, + ); console.log( chalk.green( diff --git a/packages/scripts/addon/utils.js b/packages/scripts/addon/utils.js index 8bb1a0c223..9186342ba2 100644 --- a/packages/scripts/addon/utils.js +++ b/packages/scripts/addon/utils.js @@ -6,11 +6,10 @@ export function amendPackageJSON(name, destination, isCanary) { ); packageJSON.scripts = { ...packageJSON.scripts, - 'cypress:open': `cd src/addons/${name} && NODE_ENV=test cypress open`, - test: `RAZZLE_JEST_CONFIG=src/addons/${name}/jest-addon.config.js razzle test ${ - isCanary ? '' : '--env=jest-environment-jsdom-sixteen' - } --passWithNoTests`, - 'cypress:run': `cd src/addons/${name} && NODE_ENV=test cypress run`, + test: `RAZZLE_JEST_CONFIG=src/addons/${name}/jest-addon.config.js razzle test --passWithNoTests`, + 'cypress:open': `make test-acceptance-addon ADDONPATH=src/addons/${name}`, + 'cypress:run': `make test-acceptance-addon-headless ADDONPATH=src/addons/${name}`, + 'cypress:ci:full': `make full-test-acceptance-addon ADDONPATH=src/addons/${name}`, }; fs.writeFileSync( `${destination}/package.json`, diff --git a/packages/scripts/package.json b/packages/scripts/package.json index a8eec6e2fa..6f3311e871 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "2.1.2", + "version": "2.2.1", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 0c40ed58cd7e9b78998ca8ccbcea53e417bdafd5 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Thu, 24 Nov 2022 18:21:50 +0200 Subject: [PATCH 077/326] Improve collapsing of whitespace on paste in slate block (#3806) Co-authored-by: Steve Piercy --- CHANGELOG.md | 1 + __tests__/volto-slate/deserialize.test.js | 393 ++++++++++++++++++ cypress/support/commands.js | 2 +- .../24-block-slate-format-boldlists.js | 19 +- .../volto-slate/27-block-slate-paste-html.js | 47 +++ package.json | 5 +- packages/volto-slate/README.md | 235 +---------- .../__snapshots__/TableBlockEdit.test.js.snap | 2 +- .../__snapshots__/TableBlockView.test.js.snap | 2 +- .../__snapshots__/TextBlockEdit.test.js.snap | 1 + .../src/blocks/Text/extensions/index.js | 1 + .../Text/extensions/normalizeExternalData.js | 7 + packages/volto-slate/src/blocks/Text/index.js | 2 + packages/volto-slate/src/editor/config.jsx | 2 + .../volto-slate/src/editor/deserialize.js | 80 ++-- .../src/editor/extensions/index.js | 1 + .../src/editor/extensions/insertData.js | 21 +- .../extensions/normalizeExternalData.js | 8 + .../src/editor/ui/ToolbarButton.test.js | 4 +- packages/volto-slate/src/editor/utils.js | 248 +++++++++++ test-setup-config.js | 5 + 21 files changed, 778 insertions(+), 308 deletions(-) create mode 100644 __tests__/volto-slate/deserialize.test.js create mode 100644 cypress/tests/core/volto-slate/27-block-slate-paste-html.js create mode 100644 packages/volto-slate/src/blocks/Text/extensions/normalizeExternalData.js create mode 100644 packages/volto-slate/src/editor/extensions/normalizeExternalData.js create mode 100644 packages/volto-slate/src/editor/utils.js diff --git a/CHANGELOG.md b/CHANGELOG.md index abf0a44b9a..da4717b82e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -568,6 +568,7 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa ### Bugfix - Prefer views assigned explicitly with `layout` over views based on the `@type` @iRohitSingh +- Improve collapsing of whitespace when pasting to slate text block @tiberiuichim ### Internal diff --git a/__tests__/volto-slate/deserialize.test.js b/__tests__/volto-slate/deserialize.test.js new file mode 100644 index 0000000000..4ccea708a4 --- /dev/null +++ b/__tests__/volto-slate/deserialize.test.js @@ -0,0 +1,393 @@ +import config from '@plone/volto/registry'; + +import { JSDOM } from 'jsdom'; +import { deserialize } from '@plone/volto-slate/editor/deserialize'; +import * as htmlUtils from '@plone/volto-slate/editor/utils'; +import { makeEditor } from '@plone/volto-slate/utils/editor'; +import installSlate from '@plone/volto-slate/index'; + +const tojson = (html) => { + const parsed = new JSDOM(html); + const document = parsed.window.document; + const editor = makeEditor(); + const body = + document.getElementsByTagName('google-sheets-html-origin').length > 0 + ? document.querySelector('google-sheets-html-origin > table') + : document.body; + const json = deserialize(editor, body); + return json; +}; + +describe('deserialize', () => { + beforeEach(() => { + config.blocks.blocksConfig = { + table: { + id: 'table', + }, + }; + installSlate(config); + }); + + it('accepts a DOM element', () => { + const html = '

Hello world

'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'p', + children: [ + { + text: 'Hello world', + }, + ], + }, + ]); + }); + + it('two root tags', () => { + const html = '

Hello world

something

'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'p', + children: [ + { + text: 'Hello world', + }, + ], + }, + { + type: 'p', + children: [ + { + text: 'something', + }, + ], + }, + ]); + }); + + it('takes inline elements', () => { + const html = 'hello'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'strong', + children: [ + { + text: 'hello', + }, + ], + }, + ]); + }); + + it('strips unknown elements', () => { + const html = 'hello'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'strong', + children: [{ text: 'hello' }], + }, + ]); + }); + + it('#1: strips all spaces and tabs immediately before and after a line break', () => { + const html = '

Hello \n\t\t\t\t World!\t

'; + + expect(htmlUtils.removeSpaceBeforeAfterEndLine(html)).toBe( + '

Hello\n World!\t

', + ); + }); + + it('#2: convert all tabs to spaces', () => { + const html = '

Hello\n World!\t

'; + + expect(htmlUtils.convertTabsToSpaces(html)).toBe( + '

Hello\n World!

', + ); + }); + + it('#3: convert line breaks spaces', () => { + const html = '

Hello\n World!

'; + + expect(htmlUtils.convertLineBreaksToSpaces(html)).toBe( + '

Hello World!

', + ); + }); + + it('#4: remove space follows space in sibling text node', () => { + const html = '

Hello World!

'; + + const dom = new JSDOM(html); + const body = dom.window.document.body; + const span = body.querySelector('span'); + + expect(htmlUtils.removeSpaceFollowSpace(' World!', span)).toBe('World!'); + }); + + it('#4: remove space follows space in sibling inline node', () => { + const html = '

Hello World!

'; + + const dom = new JSDOM(html); + const body = dom.window.document.body; + const span = body.querySelector('span'); + const b = body.querySelector('b'); + + expect(htmlUtils.removeSpaceFollowSpace('Hello ', b)).toBe('Hello '); + expect(htmlUtils.removeSpaceFollowSpace(' World!', span)).toBe('World!'); + }); + + it('#4: remove space in text node follows space in sibling inline node', () => { + const html = '

Hello World!

'; + + const dom = new JSDOM(html); + const body = dom.window.document.body; + const b = body.querySelector('b'); + + const textNode = b.nextSibling; + expect(textNode.textContent).toBe(' World! '); + + expect(htmlUtils.removeSpaceFollowSpace(' World! ', textNode)).toBe( + 'World! ', + ); + }); + + it('#5. Remove space at beginning of element', () => { + const html = '

Hello World!

'; + + const dom = new JSDOM(html); + const body = dom.window.document.body; + const h1 = body.querySelector('h1'); + + const textNode = h1.firstChild; + expect(textNode.textContent).toBe(' Hello '); + + expect(htmlUtils.removeElementEdges(' Hello ', textNode)).toBe('Hello '); + }); + + it('#5. Remove space at end of element', () => { + const html = '

Hello World!

'; + + const dom = new JSDOM(html); + const body = dom.window.document.body; + const h1 = body.querySelector('h1'); + + const textNode = h1.lastChild; + expect(textNode.textContent).toBe(' '); + + expect(htmlUtils.removeElementEdges(' ', textNode)).toBe(''); + }); + + it('collapses multiple consecutive spaces', () => { + const html = 'hello world'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'strong', + children: [{ text: 'hello world' }], + }, + ]); + }); + + it('preserves spaces between two inline nodes', () => { + const html = 'hello world'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'i', + children: [{ text: 'hello' }], + }, + { text: ' ' }, + { + type: 'i', + children: [{ text: 'world' }], + }, + ]); + }); + + it('preserves spaces between one block and one inline node', () => { + const html = '

hello

world'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'p', + children: [{ text: 'hello' }], + }, + { text: ' ' }, + { + type: 'i', + children: [{ text: 'world' }], + }, + ]); + }); + + it('preserves spaces between one inline node and one block node', () => { + const html = 'hello

world

'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'i', + children: [{ text: 'hello' }], + }, + { text: ' ' }, + { + type: 'p', + children: [{ text: 'world' }], + }, + ]); + }); + + it('replaces a single newline inside text with a space', () => { + const html = 'hello\nworld'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'i', + children: [{ text: 'hello world' }], + }, + ]); + }); + + it('replaces multiple newlines inside text with a space', () => { + const html = 'hello\n\n\nworld'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'i', + children: [{ text: 'hello world' }], + }, + ]); + }); + + it('removes whitespace between block elements', () => { + const html = '

hello

\n \n

world

'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'p', + children: [{ text: 'hello' }], + }, + { + type: 'p', + children: [{ text: 'world' }], + }, + ]); + }); + + it('transforms newlines at beginning of tags to space', () => { + const html = 'hello\nworld'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'b', + children: [{ text: 'hello' }], + }, + { + type: 'i', + children: [{ text: ' world' }], + }, + ]); + }); + + it('transforms newlines after a space', () => { + const html = '

Lorem Ipsum\nis simply dummy text\n

'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'p', + children: [ + { + type: 'strong', + children: [{ text: 'Lorem Ipsum' }], + }, + { text: ' is simply dummy text' }, + ], + }, + ]); + }); + + it('transforms newlines to space after an inline tag', () => { + const html = '

Lorem Ipsum\nis simply dummy text

'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'p', + children: [ + { + type: 'strong', + children: [{ text: 'Lorem Ipsum' }], + }, + { text: ' is simply dummy text' }, + ], + }, + ]); + }); + + it('it removes new lines at beginning of text of block nodes', () => { + const html = 'hello

\n world\n

dot'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'b', + children: [{ text: 'hello' }], + }, + { + type: 'p', + children: [{ text: 'world' }], + }, + { + type: 'i', + children: [{ text: 'dot' }], + }, + ]); + }); + + it('it removes consecutive space in inline nodes', () => { + const html = `hello world`; + // console.log(JSON.stringify(tojson(html), null, 2)); + + expect(tojson(html)).toStrictEqual([ + { + type: 'b', + children: [{ text: 'hello ' }], + }, + { + type: 'i', + children: [{ text: 'world' }], + }, + ]); + }); + + it('it handles text fragments', () => { + const html = + 'Lorem Ipsum\nis simply dummy text of the printing and typesetting industry.'; + + expect(tojson(html)).toStrictEqual([ + { + type: 'strong', + children: [{ text: 'Lorem Ipsum' }], + }, + { + text: ' is simply dummy text of the printing and typesetting industry.', + }, + ]); + }); + + it('handles chrome + firefox style copy', () => { + const html = `Hello world`; + + expect(tojson(html)).toStrictEqual([ + { + type: 'strong', + children: [{ text: 'Hello' }], + }, + { + text: ' ', + }, + { + text: 'world', + }, + ]); + }); +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 426661cf6f..2f5908903b 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -613,7 +613,7 @@ Cypress.Commands.add( (query, htmlContent) => { return cy .wrap(query) - .type(' ') + .type(' {backspace}') .trigger('paste', createHtmlPasteEvent(htmlContent)); }, ); diff --git a/cypress/tests/core/volto-slate/24-block-slate-format-boldlists.js b/cypress/tests/core/volto-slate/24-block-slate-format-boldlists.js index 1ebb4fb339..1971244b8f 100644 --- a/cypress/tests/core/volto-slate/24-block-slate-format-boldlists.js +++ b/cypress/tests/core/volto-slate/24-block-slate-format-boldlists.js @@ -40,31 +40,31 @@ describe('Block Tests: Bold Bulleted lists', () => { 'sleep furiously.', ); }); + it('As editor I can paste internal(slate formatted) formatted bulleted lists', function () { // Complete chained commands - cy.getSlateEditorAndType('This is slate"s own bold content'); - cy.setSlateSelection('This is slate"s own'); + cy.getSlateEditorAndType("This is slate's own bold content"); + cy.setSlateSelection("This is slate's own"); //create a bold bullted list cy.clickSlateButton('Bulleted list'); cy.clickSlateButton('Bold'); //copy content "This is slate"s own" - cy.setSlateCursor('content') - .type('{enter}') - .pasteClipboard( - 'This is slate"s own', - ); + cy.setSlateCursor('content').type('{enter}'); + cy.getSlate().pasteClipboard( + "This is slate's own", + ); // Save cy.toolbarSave(); cy.get('[id="page-document"] ul li:nth-child(1) strong').contains( - 'This is slate"s own', + "This is slate's own", ); //pasted content cy.get('[id="page-document"] ul li:nth-child(2) strong').contains( - 'This is slate"s own', + "This is slate's own", ); }); @@ -83,6 +83,7 @@ describe('Block Tests: Bold Bulleted lists', () => { // Save cy.toolbarSave(); + // cy.pause(); cy.get('[id="page-document"] ul li:nth-child(1) strong').contains( 'This is slate"s own bold content', diff --git a/cypress/tests/core/volto-slate/27-block-slate-paste-html.js b/cypress/tests/core/volto-slate/27-block-slate-paste-html.js new file mode 100644 index 0000000000..74a815f29c --- /dev/null +++ b/cypress/tests/core/volto-slate/27-block-slate-paste-html.js @@ -0,0 +1,47 @@ +import { slateBeforeEach } from '../../../support/e2e'; + +describe('Block Tests: external text containing html contents/tags ', () => { + beforeEach(slateBeforeEach); + + it('should paste external text containing html', function () { + // cy.getSlateEditorAndType('Let"s paste external html texts'); + // cy.setSlateCursor('texts').type('{enter}'); + cy.getSlate().pasteClipboard( + '

For simplicity, emissions arising (CRF 3B) were presented for all livestock type h CH4 and N2O), e CO2e value.single CO2e figure.

', + ); + + // Save + cy.toolbarSave(); + + cy.get('[id="page-document"] p').should('have.length', 1); + }); + + it('should paste external formatted text and does not split the blocks', function () { + // The idea is pasteClipboard should only apply on its attached slate block + // by not splitting them into blocks. + cy.getSlate().pasteClipboard( + `

Lorem Ipsum +is simply dummy text of the printing and typesetting industry. +

`, + ); + + // Save + cy.toolbarSave(); + cy.get('[id="page-document"] > p:nth-of-type(1)').should( + 'have.html', + `Lorem Ipsum is simply dummy text of the printing and typesetting industry.`, + ); + }); + + it('should paste external text containing empty anchor links', function () { + cy.getSlate().pasteClipboard( + `
+ `, + ); + + // Save + cy.toolbarSave(); + + cy.get('[id="page-document"] p a').should('have.length', 1); + }); +}); diff --git a/package.json b/package.json index d30f38a2e4..c0c3ed90f1 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@plone/volto/package.json": "/package.json", "@plone/volto/babel": "/babel.js", "@plone/volto/(.*)$": "/src/$1", - "@plone/volto-slate": "/packages/volto-slate/src", + "@plone/volto-slate/(.*)$": "/packages/volto-slate/src/$1", "~/config": "/src/config", "~/../locales/${lang}.json": "/locales/en.json", "(.*)/locales/(.*)": "/locales/$2", @@ -117,7 +117,8 @@ "testMatch": [ "/src/**/__tests__/**/*.{js,jsx,mjs}", "/src/**/?(*.)(spec|test).{js,jsx,mjs}", - "/__tests__/**/?(*.)(spec|test).{js,jsx,mjs}" + "/__tests__/**/?(*.)(spec|test).{js,jsx,mjs}", + "/packages/volto-slate/src/**/?(*.)(spec|test).{js,jsx,mjs}" ] }, "prettier": { diff --git a/packages/volto-slate/README.md b/packages/volto-slate/README.md index 70e2fe1516..c964f3e77e 100644 --- a/packages/volto-slate/README.md +++ b/packages/volto-slate/README.md @@ -1,235 +1,4 @@ # volto-slate -TODO: fill in - -An alternative text editor for Volto, capable of completely replacing the -default richtext editor while offering enhanced functionality and behavior. We -believe that, in order to succeed, Volto's richtext form editor (the Volto -Composite Page editor) needs strong integration between the rich text -capabilities and the rest of the Volto blocks. Some examples of the kind of -strong integration we have in mind: - -- Pasting complex documents inside a volto-slate text block will create - multiple Volto blocks: images will be converted to Volto Image blocks, tables - will be converted to Volto Table blocks, etc. -- The text block accepts drag&drop images and it will upload them as Volto Image blocks. -- volto-slate has a Table button with the familiar size input, but it create a Table block - -While this addon is still in an early alpha stage, we've solved most of the big -issues, the API starts to stabilize and we've already started several addons -based on it: https://github.com/eea/volto-slate-metadata-mentions/ and -https://github.com/eea/volto-slate-zotero - -## Why - -Some of the main reasons that drove us to create volto-slate instead of -enhancing Volto's draftjs implementation: - -- Volto's draftjs implementation depends on draft-js-plugins, a third-party - project that introduces its own set of bugs and maintanance issues -- Slate has a modern, developer-friendly api that makes developing plugins - something easy to do. Getting the editor in a plugin is as easy as `const editor = useSlate()`, overriding core functionality is something that's built - in as pluggable, directly in Slate. - -- Volto's draft based implementation depends on Redraft for its final output, - which comes with its own bugs and issues. While it is nice to have view-mode - components, this is something that volto-slate implements just as well. -- Because Slate's internal storage uses a tree modeled on the DOM pattern, its - final rendered output is very clean. Note: The Slate editor value is a JSON - object, similar to the Draftjs based implementation. - -## Upgrades - -### Upgrade to 4.x.x - -- Namespace the plugins [#156](https://github.com/eea/volto-slate/pull/156): - - Make sure you upgrade your slate plugins to use the new slate namespaced plugin ids. - See for example `volto-slate-footnote` [#23](https://github.com/eea/volto-slate-footnote/pull/23/commits/efdc07041097a6edf608b377141fba15fbee65cf) -- `asDefault` profile makes the volto-slate as the default Editor for `blocks` and `richtext`. - - If you're not ready for this, yet, switch to `volto-slate:asDefaultBlock` - -### Upgrade to 3.x.x - -- Removed all deprecated, already in Volto Core, `futurevolto` components: - - `SidebarPopup` - - `ObjectWidget` - - `ObjectBrowserWidget` - - `helpers/Blocks` -- Table `inline button` and `copy&paste` support is not installed by default anymore. - You'll need to explicitly import the `tableButton` profile like: - - `volto:asDefault,tableButton` - - `volto-slate:minimalDefault,simpleLink,tableButton` - -## Available profiles. - -volto-slate provides several optional configuration: - -- `asDefault` - makes the volto-slate as the default Editor for `blocks` and `richtext` -- `asDefaultBlock` - makes volto-slate the default Editor for `blocks` -- `asDefaultRichText` - makes volto-slate the default Editor for `richtext` widget -- `minimalDefault`, same as the above, but uses a set of toolbar buttons similar to Volto -- `simpleLink` reuses Volto's link plugin and makes for a better replacement of Volto's rich text editor. -- `tableButton` adds table button to Slate toolbar in order to easily insert Table block after. - -## Features - -### 1. Hovering (floating) toolbar that shows up on selection - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/1.gif) - -### 2. Optional expanded (fixed) toolbar - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/2.gif) - -### 3. Working with links (internal, external, email) - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/3.gif) - -### 4. Removing links - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/4.gif) - -### 5. Editing links - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/5.gif) - -### 6. Block-quotes - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/6.gif) - -### 7. Split paragraph block in two with `Enter` key and join them back with `Backspace` key - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/7.gif) - -### 8. Breaking and joining list items - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/8.gif) - -### 9. Breaking (with expanded selection) and joining list items - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/9.gif) - -### 10. Inserting a new list item at the end - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/10.gif) - -### 11. Two `Enter` key presses in the last empty list item creates a new list - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/11.gif) - -### 12. Using `Up` and `Down` keys to go through the blocks in both directions - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/12.gif) - -### 13. Changing indent level of list items using `Tab` and `Shift-Tab` keys - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/13.gif) - -### 14. Splitting a list block with `Enter` into two list blocks - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/14.gif) - -### 15. Support for markdown bulleted lists with `*`, `-` and `+` - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/15.gif) - -### 16. Support for markdown numbered lists with `1.` - `9.` - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/16.gif) - -### 17. `Backspace` with cursor on first position inside a list with just one item converts the list to a paragraph - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/17.gif) - -### 18. Creating a new text block with `Enter` at the end of a text block and removing it with `Backspace` - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/18.gif) - -### 19. Switching the list type (numbered list to/from bulleted list) - -![Screen Recording](https://raw.githubusercontent.com/eea/volto-slate/master/docs/source/images/19.gif) - -## Getting started - -### Try volto-slate with Docker - -1. Get the latest Docker images - - ``` - docker pull plone - docker pull plone/volto - ``` - -1. Start Plone backend - - ``` - docker run -d --name plone -p 8080:8080 -e SITE=Plone -e PROFILES="profile-plone.restapi:blocks" plone - ``` - -1. Start Volto frontend - - ``` - docker run -it --rm -p 3000:3000 --link plone -e ADDONS="volto-slate:asDefault" plone/volto - ``` - -1. Go to http://localhost:3000 - -1. Login with **admin:admin** - -1. Create a new **Page** and add a new **Text** block. - -### Add volto-slate to your Volto project - -1. Make sure you have a [Plone backend](https://plone.org/download) up-and-running at http://localhost:8080/Plone - -1. Start Volto frontend - -- If you already have a Volto project, just update `package.json`: - - ```JSON - "addons": [ - "volto-slate:asDefault" - ], - - "dependencies": { - "volto-slate": "*" - } - ``` - -- If not, create one: - - ``` - npm install -g yo @plone/generator-volto - yo @plone/volto my-volto-project --addon volto-slate:asDefault - cd my-volto-project - ``` - -- Install new add-ons and restart Volto: - - ``` - yarn install - yarn start - ``` - -1. Go to http://localhost:3000 - -1. Happy editing! - -## Plugins - -To write a new plugin, please refer [plugins](https://github.com/eea/volto-slate/tree/master/docs/source/volto-slate-plugins.md) - -## Cypress - -We aim to achieve a good coverage for cypress. Please refer to commands listed [here](https://github.com/eea/volto-slate/tree/master/docs/source/testing.md) to ease the process of writing new tests. - -## Copyright and license - -The Initial Owner of the Original Code is European Environment Agency (EEA). -All Rights Reserved. - -See [LICENSE.md](https://github.com/eea/volto-slate/blob/master/LICENSE.md) for details. - -## Funding - -[European Environment Agency (EU)](http://eea.europa.eu) +Implementation of the Volto text block using the +[Slate](https://www.slatejs.org/) JavaScript editor framework. diff --git a/packages/volto-slate/src/blocks/Table/__snapshots__/TableBlockEdit.test.js.snap b/packages/volto-slate/src/blocks/Table/__snapshots__/TableBlockEdit.test.js.snap index dd490441d8..3e01e9d0fe 100644 --- a/packages/volto-slate/src/blocks/Table/__snapshots__/TableBlockEdit.test.js.snap +++ b/packages/volto-slate/src/blocks/Table/__snapshots__/TableBlockEdit.test.js.snap @@ -11,7 +11,7 @@ exports[`renders an edit table block component 1`] = ` className="" >

diff --git a/packages/volto-slate/src/blocks/Text/__snapshots__/TextBlockEdit.test.js.snap b/packages/volto-slate/src/blocks/Text/__snapshots__/TextBlockEdit.test.js.snap index 678045df52..694a2140c4 100644 --- a/packages/volto-slate/src/blocks/Text/__snapshots__/TextBlockEdit.test.js.snap +++ b/packages/volto-slate/src/blocks/Text/__snapshots__/TextBlockEdit.test.js.snap @@ -13,6 +13,7 @@ exports[`TextBlockEdit renders w/o errors 1`] = ` class="toolbar-wrapper" />
{ + return fragment; // don't normalize in text block + }; + + return editor; +} diff --git a/packages/volto-slate/src/blocks/Text/index.js b/packages/volto-slate/src/blocks/Text/index.js index 69f23cd8d5..8082b45296 100644 --- a/packages/volto-slate/src/blocks/Text/index.js +++ b/packages/volto-slate/src/blocks/Text/index.js @@ -26,6 +26,7 @@ import { withLists, withSplitBlocksOnBreak, withIsSelected, + normalizeExternalData, } from './extensions'; import { extractImages } from '@plone/volto-slate/editor/plugins/Image/deconstruct'; import { extractTables } from '@plone/volto-slate/blocks/Table/deconstruct'; @@ -44,6 +45,7 @@ export default (config) => { withDeserializers, withIsSelected, breakList, + normalizeExternalData, ], // Pluggable handlers for the onKeyDown event of diff --git a/packages/volto-slate/src/editor/config.jsx b/packages/volto-slate/src/editor/config.jsx index 660d563446..6b2a3dd66f 100644 --- a/packages/volto-slate/src/editor/config.jsx +++ b/packages/volto-slate/src/editor/config.jsx @@ -32,6 +32,7 @@ import { withDeleteSelectionOnEnter, withDeserializers, normalizeNode, + normalizeExternalData, } from './extensions'; import { // inlineTagDeserializer, @@ -198,6 +199,7 @@ export const extensions = [ insertData, isInline, normalizeNode, + normalizeExternalData, ]; // Default hotkeys and the format they trigger diff --git a/packages/volto-slate/src/editor/deserialize.js b/packages/volto-slate/src/editor/deserialize.js index e86831b2c7..bdf1aa988b 100644 --- a/packages/volto-slate/src/editor/deserialize.js +++ b/packages/volto-slate/src/editor/deserialize.js @@ -1,70 +1,37 @@ import { jsx } from 'slate-hyperscript'; import { Text } from 'slate'; -import { isWhitespace } from '@plone/volto-slate/utils'; -import { - TD, - TH, - COMMENT, - ELEMENT_NODE, - INLINE_ELEMENTS, - TEXT_NODE, -} from '../constants'; - -const isInline = (node) => - node && - (node.nodeType === TEXT_NODE || INLINE_ELEMENTS.includes(node.nodeName)); +// import { isWhitespace } from '@plone/volto-slate/utils'; +import { TD, TH, COMMENT, ELEMENT_NODE, TEXT_NODE } from '../constants'; + +import { collapseInlineSpace } from './utils'; /** - * Deserialize to an object or an Array. + * Deserialize to a Slate Node, an Array of Slate Nodes or null + * + * One particularity of this function is that it tries to do + * a "perception-based" conversion. For example, in html, multiple whitespaces + * display as a single space. A new line character in text is actually rendered + * as a space, etc. So we try to meet user's expectations that when they + * copy/paste content, we'll preserve the aspect of their text. * - * This returns a Slate Node or null. + * See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace */ export const deserialize = (editor, el) => { - // console.log('deserialize el:', el); const { htmlTagsToSlate } = editor; - // console.log('des:', el.nodeType, el); if (el.nodeType === COMMENT) { return null; } else if (el.nodeType === TEXT_NODE) { - // instead of === '\n' we use isWhitespace for when deserializing tables - // from Calc and other similar cases - - // console.log('maybe whitespace', { - // text: `-${el.textContent}-`, - // prev: el.previousSibling, - // next: el.nextSibling, - // isPrev: isInline(el.previousSibling), - // isNext: isInline(el.nextSibling), - // prevName: el.previousSibling && el.previousSibling.nodeName, - // nextName: el.nextSibling && el.nextSibling.nodeName, - // }); - - if (isWhitespace(el.textContent)) { - // console.log({ - // text: `-${el.textContent}-`, - // prev: el.previousSibling, - // next: el.nextSibling, - // isPrev: isInline(el.previousSibling), - // isNext: isInline(el.nextSibling), - // prevName: el.previousSibling && el.previousSibling.nodeName, - // nextName: el.nextSibling && el.nextSibling.nodeName, - // }); - // if it's empty text between 2 tags, it should be ignored - return isInline(el.previousSibling) || isInline(el.nextSibling) - ? { text: el.textContent } // perceptually multiple whitespace render as a single space - : null; - } - return { - text: el.textContent - .replace(/\n$/g, ' ') - .replace(/\n/g, ' ') - .replace(/\t/g, ''), - }; + const text = collapseInlineSpace(el); + return text + ? { + text, + } + : null; } else if (el.nodeType !== ELEMENT_NODE) { return null; } else if (el.nodeName === 'BR') { - // TODO: is handling
like this ok in all cases ? + // gets merged with sibling text nodes by Slate normalization in insertData return { text: '\n' }; } @@ -78,7 +45,8 @@ export const deserialize = (editor, el) => { return htmlTagsToSlate[nodeName](editor, el); } - return deserializeChildren(el, editor); // fallback deserializer + // fallback deserializer, all unknown elements are "stripped" + return deserializeChildren(el, editor); }; export const typeDeserialize = (editor, el) => { @@ -138,10 +106,11 @@ export const inlineTagDeserializer = (attrs) => (editor, el) => { export const spanTagDeserializer = (editor, el) => { const style = el.getAttribute('style') || ''; let children = el.childNodes; + if ( // handle formatting from OpenOffice children.length === 1 && - children[0].nodeType === 3 && + children[0].nodeType === TEXT_NODE && children[0].textContent === '\n' ) { return jsx('text', {}, ' '); @@ -149,7 +118,7 @@ export const spanTagDeserializer = (editor, el) => { children = deserializeChildren(el, editor); // whitespace is replaced by deserialize() with null; - children = children.map((c) => (c === null ? ' ' : c)); + children = children.map((c) => (c === null ? '' : c)); // TODO: handle sub/sup as and // Handle Google Docs' formatting @@ -171,6 +140,7 @@ export const spanTagDeserializer = (editor, el) => { const res = children.find((c) => typeof c !== 'string') ? children : jsx('text', {}, children); + return res; }; diff --git a/packages/volto-slate/src/editor/extensions/index.js b/packages/volto-slate/src/editor/extensions/index.js index 80364f647a..6f7d5ed036 100644 --- a/packages/volto-slate/src/editor/extensions/index.js +++ b/packages/volto-slate/src/editor/extensions/index.js @@ -3,3 +3,4 @@ export * from './insertData'; export * from './isInline'; export * from './withDeserializers'; export * from './normalizeNode'; +export * from './normalizeExternalData'; diff --git a/packages/volto-slate/src/editor/extensions/insertData.js b/packages/volto-slate/src/editor/extensions/insertData.js index 9a13d16805..fbeb271a26 100644 --- a/packages/volto-slate/src/editor/extensions/insertData.js +++ b/packages/volto-slate/src/editor/extensions/insertData.js @@ -29,14 +29,27 @@ export const insertData = (editor) => { let fragment; + // eslint-disable-next-line no-console + console.debug('clipboard operation', { + clipboard: dt, + parsedBody: body, + }); + const val = deserialize(editor, body); fragment = Array.isArray(val) ? val : [val]; - - // external normalization - fragment = normalizeExternalData(editor, fragment); + fragment = editor.normalizeExternalData(fragment); editor.insertFragment(fragment); + // eslint-disable-next-line no-console + console.debug('result clipboard operation', { + clipboard: dt, + parsedBody: body, + deserializedValue: val, + normalizedFragment: fragment, + editorChildren: editor.children, + }); + return true; }, 'text/plain': (dt, fullMime) => { @@ -94,6 +107,7 @@ export const insertData = (editor) => { } } + // always normalize when dealing with plain text const nodes = normalizeExternalData(editor, fragment); if (!containsText) { Transforms.insertNodes(editor, nodes); @@ -116,7 +130,6 @@ export const insertData = (editor) => { editor.beforeInsertData(data); } - // debugger; for (let i = 0; i < editor.dataTransferFormatsOrder.length; ++i) { const dt = editor.dataTransferFormatsOrder[i]; if (dt === 'files') { diff --git a/packages/volto-slate/src/editor/extensions/normalizeExternalData.js b/packages/volto-slate/src/editor/extensions/normalizeExternalData.js new file mode 100644 index 0000000000..504aada7a3 --- /dev/null +++ b/packages/volto-slate/src/editor/extensions/normalizeExternalData.js @@ -0,0 +1,8 @@ +import { normalizeExternalData as normalize } from '@plone/volto-slate/utils'; + +export function normalizeExternalData(editor) { + editor.normalizeExternalData = (fragment) => { + return normalize(fragment); + }; + return editor; +} diff --git a/packages/volto-slate/src/editor/ui/ToolbarButton.test.js b/packages/volto-slate/src/editor/ui/ToolbarButton.test.js index 40c1bb6bb0..eebf2335e4 100644 --- a/packages/volto-slate/src/editor/ui/ToolbarButton.test.js +++ b/packages/volto-slate/src/editor/ui/ToolbarButton.test.js @@ -1,7 +1,7 @@ import React from 'react'; import configureStore from 'redux-mock-store'; import { Provider } from 'react-intl-redux'; -import { wait, render } from '@testing-library/react'; +import { waitFor, render } from '@testing-library/react'; import ToolbarButton from './ToolbarButton'; @@ -20,6 +20,6 @@ describe('ToolbarButton', () => { , ); - await wait(() => expect(asFragment()).toMatchSnapshot()); + await waitFor(() => expect(asFragment()).toMatchSnapshot()); }); }); diff --git a/packages/volto-slate/src/editor/utils.js b/packages/volto-slate/src/editor/utils.js new file mode 100644 index 0000000000..136e2e8871 --- /dev/null +++ b/packages/volto-slate/src/editor/utils.js @@ -0,0 +1,248 @@ +import { INLINE_ELEMENTS, TEXT_NODE } from '../constants'; + +// Original at https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace +/** + * Throughout, whitespace is defined as one of the characters + * "\t" TAB \u0009 + * "\n" LF \u000A + * "\r" CR \u000D + * " " SPC \u0020 + * + * This does not use JavaScript's "\s" because that includes non-breaking + * spaces (and also some other characters). + */ + +/** + * Determine whether a node's text content is entirely whitespace. + * + * @param nod A node implementing the |CharacterData| interface (i.e., + * a |Text|, |Comment|, or |CDATASection| node + * @return True if all of the text content of |nod| is whitespace, + * otherwise false. + */ +export function is_all_ws(text) { + return !/[^\t\n\r ]/.test(text); +} + +/** + * Version of |data| that doesn't include whitespace at the beginning + * and end and normalizes all whitespace to a single space. (Normally + * |data| is a property of text nodes that gives the text of the node.) + * + * @param txt The text node whose data should be returned + * @return A string giving the contents of the text node with + * whitespace collapsed. + */ +export function data_of(txt) { + let data = txt.textContent; + data = data.replace(/[\t\n\r ]+/g, ' '); + if (data[0] === ' ') { + data = data.substring(1, data.length); + } + if (data[data.length - 1] === ' ') { + data = data.substring(0, data.length - 1); + } + return data; +} + +/** + * Determine if a node should be ignored by the iterator functions. + * + * @param nod An object implementing the DOM1 |Node| interface. + * @return true if the node is: + * 1) A |Text| node that is all whitespace + * 2) A |Comment| node + * and otherwise false. + */ + +export function is_ignorable(nod) { + return ( + nod.nodeType === 8 || // A comment node + (nod.nodeType === 3 && is_all_ws(nod.textContent)) + ); // a text node, all ws +} + +/** + * Version of |previousSibling| that skips nodes that are entirely + * whitespace or comments. (Normally |previousSibling| is a property + * of all DOM nodes that gives the sibling node, the node that is + * a child of the same parent, that occurs immediately before the + * reference node.) + * + * @param sib The reference node. + * @return Either: + * 1) The closest previous sibling to |sib| that is not + * ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ +export function node_before(sib) { + while ((sib = sib.previousSibling)) { + if (!is_ignorable(sib)) { + return sib; + } + } + return null; +} + +/** + * Version of |nextSibling| that skips nodes that are entirely + * whitespace or comments. + * + * @param sib The reference node. + * @return Either: + * 1) The closest next sibling to |sib| that is not + * ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ +export function node_after(sib) { + while ((sib = sib.nextSibling)) { + if (!is_ignorable(sib)) { + return sib; + } + } + return null; +} + +/** + * Version of |lastChild| that skips nodes that are entirely + * whitespace or comments. (Normally |lastChild| is a property + * of all DOM nodes that gives the last of the nodes contained + * directly in the reference node.) + * + * @param sib The reference node. + * @return Either: + * 1) The last child of |sib| that is not + * ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ +export function last_child(par) { + let res = par.lastChild; + while (res) { + if (!is_ignorable(res)) { + return res; + } + res = res.previousSibling; + } + return null; +} + +/** + * Version of |firstChild| that skips nodes that are entirely + * whitespace and comments. + * + * @param sib The reference node. + * @return Either: + * 1) The first child of |sib| that is not + * ignorable according to |is_ignorable|, or + * 2) null if no such node exists. + */ +export function first_child(par) { + let res = par.firstChild; + while (res) { + if (!is_ignorable(res)) { + return res; + } + res = res.nextSibling; + } + return null; +} + +export const removeSpaceBeforeAfterEndLine = (text) => { + text = text.replace(/\s+\n/gm, '\n'); // space before endline + text = text.replace(/\n\s+/gm, '\n'); // space after endline + return text; +}; + +export const convertTabsToSpaces = (text) => text.replace(/\t/gm, ' '); +export const convertLineBreaksToSpaces = (text) => text.replace(/\n/gm, ' '); + +export const isInline = (node) => + node && + (node.nodeType === TEXT_NODE || INLINE_ELEMENTS.includes(node.nodeName)); + +export const removeSpaceFollowSpace = (text, node) => { + // Any space immediately following another space (even across two separate + // inline elements) is ignored (rule 4) + text = text.replace(/ ( +)/gm, ' '); + if (!text.startsWith(' ')) return text; + + if (node.previousSibling) { + if (node.previousSibling.nodeType === TEXT_NODE) { + if (node.previousSibling.textContent.endsWith(' ')) { + return text.replace(/^ /, ''); + } + } else if (isInline(node.previousSibling)) { + const prevText = collapseInlineSpace(node.previousSibling); + if (prevText.endsWith(' ')) { + return text.replace(/^ /, ''); + } + } + } else { + const parent = node.parentNode; + if (parent.previousSibling) { + // && isInline(parent.previousSibling) + const prevText = collapseInlineSpace(parent.previousSibling); + if (prevText && prevText.endsWith(' ')) { + return text.replace(/^ /, ''); + } + } + } + + return text; +}; + +export const removeElementEdges = (text, node) => { + if ( + !isInline(node.parentNode) && + !node.previousSibling && + text.match(/^\s/) + ) { + text = text.replace(/^\s+/, ''); + } + + if (text.match(/\s$/) && !node.nextSibling && !isInline(node.parentNode)) { + text = text.replace(/\s$/, ''); + } + + return text; +}; + +export const collapseInlineSpace = (node) => { + let text = node.textContent; + + // See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace + + // 1. all spaces and tabs immediately before and after a line break are ignored + + text = removeSpaceBeforeAfterEndLine(text); + + // 2. Next, all tab characters are handled as space characters + text = convertTabsToSpaces(text); + + // 3. Convert all line breaks to spaces + text = convertLineBreaksToSpaces(text); + + // 4. Any space immediately following another space + // (even across two separate inline elements) is ignored + text = removeSpaceFollowSpace(text, node); + + // 5. Sequences of spaces at the beginning and end of an element are removed + text = removeElementEdges(text, node); + + // (volto) Return null if the element is not adjacent to an inline node + // This will cause the element to be ignored in the deserialization + // TODO: use the node traverse functions defined here + if ( + is_all_ws(text) && + !( + isInline(node.previousSibling) || + isInline(node.nextSibling) || + isInline(node.parentNode.nextSibling) || + isInline(node.parentNode.previousSibling) + ) + ) { + return null; + } + + return text; +}; diff --git a/test-setup-config.js b/test-setup-config.js index 029669989b..49960e67c0 100644 --- a/test-setup-config.js +++ b/test-setup-config.js @@ -165,3 +165,8 @@ config.set('widgets', { }); config.set('components', {}); +config.set('experimental', { + addBlockButton: { + enabled: false, + }, +}); From 40f6559d6673678502b86266014e388f3fa6bf3e Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Thu, 24 Nov 2022 18:22:40 +0200 Subject: [PATCH 078/326] Fix crash in slate toolbar on html richtext field (#3972) --- CHANGELOG.md | 1 + packages/volto-slate/src/editor/ui/Toolbar.jsx | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da4717b82e..dd5ea7357b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Fix addons loader test @tiberiuichim - Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh - Fix user search by full name in users control panel @reebalazs +- Fix crash in Slate link editing in a dexterity field @tiberiuichim ### Internal diff --git a/packages/volto-slate/src/editor/ui/Toolbar.jsx b/packages/volto-slate/src/editor/ui/Toolbar.jsx index 5e884f7876..2d7406e12f 100644 --- a/packages/volto-slate/src/editor/ui/Toolbar.jsx +++ b/packages/volto-slate/src/editor/ui/Toolbar.jsx @@ -20,7 +20,7 @@ const Toolbar = ({ useEffect(() => { const domNode = ref.current; - let rect; + let rect = { width: 1, top: 0, left: 0 }; if ((children || []).length === 0) { domNode.removeAttribute('style'); @@ -63,8 +63,13 @@ const Toolbar = ({ // TODO: should we fallback to editor.getSelection()? // TODO: test with third party plugins const slateNode = Node.get(editor, selection.anchor.path); - const domEl = ReactEditor.toDOMNode(editor, slateNode); - rect = domEl.getBoundingClientRect(); + try { + const domEl = ReactEditor.toDOMNode(editor, slateNode); + rect = domEl.getBoundingClientRect(); + } catch { + // ignoring error here is safe, editor is out of sync and the selection + // is actually none, so no toolbar should be shown + } } domNode.style.opacity = 1; From 7f5bf0248ce60a3a23c57f179e0f1c8df1611b91 Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Thu, 24 Nov 2022 13:25:30 -0300 Subject: [PATCH 079/326] Internationalization add urser form (#3974) --- .gitignore | 2 ++ CHANGELOG.md | 1 + locales/ca/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/de/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/en/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/es/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/eu/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/fr/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/it/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/ja/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/nl/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/pt/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/pt_BR/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/ro/LC_MESSAGES/volto.po | 20 +++++++++++++++++ locales/volto.pot | 22 ++++++++++++++++++- .../Controlpanels/Users/UsersControlpanel.jsx | 21 ++++++++++-------- src/helpers/MessageLabels/MessageLabels.js | 18 +++++++++++++++ 17 files changed, 294 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 18ffb13b0d..cdbc798738 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ lighthouse-report.html .vscode/ .#* *~ +/.settings/ +.*project # Python /api/.installed.cfg diff --git a/CHANGELOG.md b/CHANGELOG.md index dd5ea7357b..65ca1b74b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Feature +- Internationalization of descriptions of user add form fields. @wesleybl - Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim - Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index 2bb52e2de4..a12fe74ff2 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -3988,6 +3988,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index 6a46175496..af1cd9d9ff 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -3985,6 +3985,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 232d9d95d1..e280c4f09f 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -3979,6 +3979,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index b2a8c27d59..bf7cb2f3c6 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -3990,6 +3990,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "El nombre de usuario es necesario para restablecer su contraseña." +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index a78f500fb5..0dffb3677d 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -3986,6 +3986,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "Erabiltzaile-izena derrigorrezkoa da zure pasahitza berrezartzeko." +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 2dc6f9d9cd..157b969a5f 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -3996,6 +3996,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index fa076a7f19..0a271091ca 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -3979,6 +3979,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "Il tuo username è richiesto per reimpostare la tua password." +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index 36cd4c2d0f..13a5d507d9 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -3987,6 +3987,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index 8deeeb573d..2da518f1be 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -3986,6 +3986,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "Gebruikersnaam is verplicht om het wachtwoor dte resetten." +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index 8d853ba007..9f34362b34 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -3987,6 +3987,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index 602b7c4a78..405fad12db 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -3989,6 +3989,26 @@ msgstr "Seu site está atualizado." msgid "Your usernaame is required for reset your password." msgstr "Seu nome de usuário é necessário para redefinir sua senha." +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "Informe seu endereço de E-Mail. Isso é necessário caso você esqueça sua senha. Nós respeitamos sua privacidade e não iremos fornecer seu endereço a terceiros ou divulgá-lo em nenhum lugar." + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "Informe seu nome completo, por exemplo, João da Silva." + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "Informe a sua nova senha. Mínimo de 8 caracteres." + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "Informe o nome do usuário que você deseja, geralmente algo como 'jsilva'. Não use espaços ou caracteres especiais. Nomes de usuários e senhas são sensíveis a letras maiúsculas e minúsculas, certifique-se que a tecla Caps Lock não esteja ativa. Esse é o nome que você usará para acessar." + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index c12f29b67c..823ec90e0f 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -3979,6 +3979,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/locales/volto.pot b/locales/volto.pot index 61bd009b74..15f49cc6f9 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2022-11-16T16:45:46.315Z\n" +"POT-Creation-Date: 2022-11-24T12:41:05.488Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -3981,6 +3981,26 @@ msgstr "" msgid "Your usernaame is required for reset your password." msgstr "" +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +msgid "addUserFormEmailDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter full name, e.g. John Smith. +msgid "addUserFormFullnameDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter your new password. Minimum 8 characters. +msgid "addUserFormPasswordDescription" +msgstr "" + +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. +msgid "addUserFormUsernameDescription" +msgstr "" + #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" diff --git a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx index 17a983b673..a6c89ba650 100644 --- a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +++ b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx @@ -439,23 +439,27 @@ class UsersControlpanel extends Component { messages.addUserFormUsernameTitle, ), type: 'string', - description: - 'Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in.', + description: this.props.intl.formatMessage( + messages.addUserFormUsernameDescription, + ), }, fullname: { title: this.props.intl.formatMessage( messages.addUserFormFullnameTitle, ), type: 'string', - description: 'Enter full name, e.g. John Smith.', + description: this.props.intl.formatMessage( + messages.addUserFormFullnameDescription, + ), }, email: { title: this.props.intl.formatMessage( messages.addUserFormEmailTitle, ), type: 'string', - description: - 'Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere.', + description: this.props.intl.formatMessage( + messages.addUserFormEmailDescription, + ), widget: 'email', }, password: { @@ -463,8 +467,9 @@ class UsersControlpanel extends Component { messages.addUserFormPasswordTitle, ), type: 'password', - description: - 'Enter your new password. Minimum 5 characters.', + description: this.props.intl.formatMessage( + messages.addUserFormPasswordDescription, + ), widget: 'password', }, sendPasswordReset: { @@ -480,7 +485,6 @@ class UsersControlpanel extends Component { type: 'array', choices: this.props.roles.map((role) => [role.id, role.id]), noValueOption: false, - description: '', }, groups: { title: this.props.intl.formatMessage( @@ -492,7 +496,6 @@ class UsersControlpanel extends Component { group.id, ]), noValueOption: false, - description: '', }, }, required: ['username', 'email'], diff --git a/src/helpers/MessageLabels/MessageLabels.js b/src/helpers/MessageLabels/MessageLabels.js index b77806d326..d0a91dea3f 100644 --- a/src/helpers/MessageLabels/MessageLabels.js +++ b/src/helpers/MessageLabels/MessageLabels.js @@ -114,6 +114,24 @@ export const messages = defineMessages({ id: 'Username', defaultMessage: 'Username', }, + addUserFormUsernameDescription: { + id: 'addUserFormUsernameDescription', + defaultMessage: + 'Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in.', + }, + addUserFormFullnameDescription: { + id: 'addUserFormFullnameDescription', + defaultMessage: 'Enter full name, e.g. John Smith.', + }, + addUserFormEmailDescription: { + id: 'addUserFormEmailDescription', + defaultMessage: + 'Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere.', + }, + addUserFormPasswordDescription: { + id: 'addUserFormPasswordDescription', + defaultMessage: 'Enter your new password. Minimum 8 characters.', + }, addGroupsFormTitleTitle: { id: 'Title', defaultMessage: 'Title', From 053b3d4375f339841a29c4208fb432952c7463a9 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Fri, 25 Nov 2022 10:46:43 +0200 Subject: [PATCH 080/326] Fix warning in number widget (#3973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 1 + src/components/manage/Widgets/NumberWidget.jsx | 2 +- .../manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ca1b74b2..0fee856f9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fix addons loader test @tiberiuichim - Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh - Fix user search by full name in users control panel @reebalazs +- Avoid warning for missing value in NumberWidget @tiberiuichim - Fix crash in Slate link editing in a dexterity field @tiberiuichim ### Internal diff --git a/src/components/manage/Widgets/NumberWidget.jsx b/src/components/manage/Widgets/NumberWidget.jsx index d68c082b2c..a019ba4532 100644 --- a/src/components/manage/Widgets/NumberWidget.jsx +++ b/src/components/manage/Widgets/NumberWidget.jsx @@ -43,7 +43,7 @@ const NumberWidget = (props) => { disabled={isDisabled} min={minimum || null} max={maximum || null} - value={value} + value={value ?? ''} placeholder={placeholder} onChange={({ target }) => onChange(id, target.value === '' ? undefined : target.value) diff --git a/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap b/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap index 37be89be37..03bc4e5635 100644 --- a/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap +++ b/src/components/manage/Widgets/__snapshots__/NumberWidget.test.jsx.snap @@ -40,7 +40,7 @@ Array [ onChange={[Function]} onClick={[Function]} type="number" - value={null} + value="" />

From 152abca894d1a014681fb71eee4f812a5322467f Mon Sep 17 00:00:00 2001 From: Alin Voinea Date: Fri, 25 Nov 2022 10:55:47 +0200 Subject: [PATCH 081/326] Provide a default View/Edit component for blocks - refs #3767 (#3776) Co-authored-by: Tiberiu Ichim Co-authored-by: Tiberiu Ichim --- CHANGELOG.md | 3 +- cypress/tests/coresandbox/defaultview.js | 39 ++++++++++ docs/source/blocks/anatomy.md | 22 ++++++ locales/ca/LC_MESSAGES/volto.po | 2 + locales/de/LC_MESSAGES/volto.po | 2 + locales/en/LC_MESSAGES/volto.po | 2 + locales/es/LC_MESSAGES/volto.po | 2 + locales/eu/LC_MESSAGES/volto.po | 2 + locales/fr/LC_MESSAGES/volto.po | 2 + locales/it/LC_MESSAGES/volto.po | 2 + locales/ja/LC_MESSAGES/volto.po | 2 + locales/nl/LC_MESSAGES/volto.po | 2 + locales/pt/LC_MESSAGES/volto.po | 2 + locales/pt_BR/LC_MESSAGES/volto.po | 2 + locales/ro/LC_MESSAGES/volto.po | 2 + locales/volto.pot | 2 + packages/coresandbox/src/index.js | 56 +++++++++++++ src/components/index.js | 3 + .../manage/Blocks/Block/DefaultEdit.jsx | 44 +++++++++++ .../manage/Blocks/Block/DefaultView.jsx | 78 +++++++++++++++++++ src/components/manage/Blocks/Block/Edit.jsx | 5 +- src/components/theme/Error/ErrorBoundary.jsx | 29 +++++++ .../theme/Error/ErrorBoundary.test.jsx | 34 ++++++++ .../__snapshots__/ErrorBoundary.test.jsx.snap | 9 +++ src/components/theme/View/RenderBlocks.jsx | 4 +- 25 files changed, 347 insertions(+), 5 deletions(-) create mode 100644 cypress/tests/coresandbox/defaultview.js create mode 100644 src/components/manage/Blocks/Block/DefaultEdit.jsx create mode 100644 src/components/manage/Blocks/Block/DefaultView.jsx create mode 100644 src/components/theme/Error/ErrorBoundary.jsx create mode 100644 src/components/theme/Error/ErrorBoundary.test.jsx create mode 100644 src/components/theme/Error/__snapshots__/ErrorBoundary.test.jsx.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fee856f9f..0d929c88de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Internationalization of descriptions of user add form fields. @wesleybl - Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim - Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh +- Provide a default View/Edit component for blocks @avoinea, @tiberiuichim ### Bugfix @@ -488,8 +489,6 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa - Upgrade `husky` to latest version @sneridagh - Enable the use of yarn 3 in the build by default @sneridagh -See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more information. - ### Feature - Japanese translation updated @terapyon diff --git a/cypress/tests/coresandbox/defaultview.js b/cypress/tests/coresandbox/defaultview.js new file mode 100644 index 0000000000..e3955c0dce --- /dev/null +++ b/cypress/tests/coresandbox/defaultview.js @@ -0,0 +1,39 @@ +context('Block Default View / Edit Acceptance Tests', () => { + describe('Block Default View / Edit', () => { + beforeEach(() => { + // given a logged in editor and a page in edit mode + cy.visit('/'); + cy.autologin(); + cy.createContent({ + contentType: 'Document', + contentId: 'document', + contentTitle: 'Test document', + }); + cy.visit('/document'); + cy.waitForResourceToLoad('@navigation'); + cy.waitForResourceToLoad('@breadcrumbs'); + cy.waitForResourceToLoad('@actions'); + cy.waitForResourceToLoad('@types'); + cy.waitForResourceToLoad('document'); + cy.navigate('/document/edit'); + cy.getSlateTitle(); + }); + + it('I can add a block with Default View / Edit based on schema and interact with it', function () { + cy.getSlate().click(); + cy.get('.button .block-add-button').click({ force: true }); + cy.get('.blocks-chooser .mostUsed .button.testBlockDefaultView').click(); + + cy.get('#field-fieldAfterObjectList') + .click() + .type('Colorless green ideas sleep furiously.'); + + cy.get('.page-block').contains('Colorless green ideas sleep furiously.'); + + cy.get('#toolbar-save').click(); + cy.get('[id="page-document"]').contains( + 'Colorless green ideas sleep furiously.', + ); + }); + }); +}); diff --git a/docs/source/blocks/anatomy.md b/docs/source/blocks/anatomy.md index 64a2099749..4c17b89ce9 100644 --- a/docs/source/blocks/anatomy.md +++ b/docs/source/blocks/anatomy.md @@ -77,3 +77,25 @@ The edit component of a block receives these props from the Blocks Engine: You can use all these props to render your edit block and model its behavior. +## Default block edit and view components + +Volto later then 16.0.0 ships with a set of default Edit and View components. +The view component is mostly a placeholder, with an auto-generated listing of +the block fields, while the default Edit component is the most interesting, as +it can use the `schema` that you can specify in the block configuration to +automatically render a form for the Block settings, in the Volto Sidebar. In +the main editing area, it will render the view component, so for many blocks +you can just develop a schema and the View component. + +To use the default Edit and/or View component, just don't set any value in the +block configuration: + +```js +config.blocks.blocksConfig.myBlock = { + id: 'myBlock', + title: "My block", + edit: null, // or simply omit it + view: null, // or simply omit it + // ... the rest of the settings +} +``` diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index a12fe74ff2..2af00f3145 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -1726,6 +1726,7 @@ msgstr "Setmana(es)" msgid "Interval Yearly" msgstr "Any(s)" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3595,6 +3596,7 @@ msgstr "Unificat" msgid "Uninstall" msgstr "Desinstal·lar" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index af1cd9d9ff..9c16d45800 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -1723,6 +1723,7 @@ msgstr "Wöchentlich" msgid "Interval Yearly" msgstr "Jährliches Intervall" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3592,6 +3593,7 @@ msgstr "Vereinigt" msgid "Uninstall" msgstr "Deinstallieren" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index e280c4f09f..b24bed3767 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -1717,6 +1717,7 @@ msgstr "" msgid "Interval Yearly" msgstr "" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3586,6 +3587,7 @@ msgstr "" msgid "Uninstall" msgstr "" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index bf7cb2f3c6..783599e75c 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -1728,6 +1728,7 @@ msgstr "Intervalo semanal" msgid "Interval Yearly" msgstr "Intervalo anual" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3597,6 +3598,7 @@ msgstr "Unificado" msgid "Uninstall" msgstr "Desinstalar" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index 0dffb3677d..f0a6d7901c 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -1724,6 +1724,7 @@ msgstr "astean behin" msgid "Interval Yearly" msgstr "urtean behin" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3593,6 +3594,7 @@ msgstr "Elkartuta" msgid "Uninstall" msgstr "Desinstalatu" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 157b969a5f..beca31d6ec 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -1734,6 +1734,7 @@ msgstr "" msgid "Interval Yearly" msgstr "" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3603,6 +3604,7 @@ msgstr "Unifié" msgid "Uninstall" msgstr "Désinstaller" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index 0a271091ca..5d44a85ea9 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -1717,6 +1717,7 @@ msgstr "settimane" msgid "Interval Yearly" msgstr "anni" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3586,6 +3587,7 @@ msgstr "Unificato" msgid "Uninstall" msgstr "Disinstalla" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index 13a5d507d9..c50d72525f 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -1725,6 +1725,7 @@ msgstr "週" msgid "Interval Yearly" msgstr "年" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3594,6 +3595,7 @@ msgstr "Unified (未翻訳)" msgid "Uninstall" msgstr "アンインストール" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index 2da518f1be..920ea96c81 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -1724,6 +1724,7 @@ msgstr "Wekelijks interval" msgid "Interval Yearly" msgstr "Jaarlijsk interval" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3593,6 +3594,7 @@ msgstr "Unified" msgid "Uninstall" msgstr "Verwijderen" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index 9f34362b34..faa3566d07 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -1725,6 +1725,7 @@ msgstr "" msgid "Interval Yearly" msgstr "" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3594,6 +3595,7 @@ msgstr "Unificado" msgid "Uninstall" msgstr "" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index 405fad12db..ddecf194b8 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -1727,6 +1727,7 @@ msgstr "Intervalo Semanal" msgid "Interval Yearly" msgstr "Intervalo Anual" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3596,6 +3597,7 @@ msgstr "Unificado" msgid "Uninstall" msgstr "Desinstalar" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index 823ec90e0f..6b5f4d2341 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -1717,6 +1717,7 @@ msgstr "Interval Lunar" msgid "Interval Yearly" msgstr "Interval Anual" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3586,6 +3587,7 @@ msgstr "Unificat" msgid "Uninstall" msgstr "Dezinstalare" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/locales/volto.pot b/locales/volto.pot index 15f49cc6f9..575980a8ce 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1719,6 +1719,7 @@ msgstr "" msgid "Interval Yearly" msgstr "" +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" @@ -3588,6 +3589,7 @@ msgstr "" msgid "Uninstall" msgstr "" +#: components/manage/Blocks/Block/DefaultView #: components/manage/Blocks/Block/Edit #: components/theme/View/RenderBlocks # defaultMessage: Unknown Block {block} diff --git a/packages/coresandbox/src/index.js b/packages/coresandbox/src/index.js index 6fc4cceaa1..4be2de670a 100644 --- a/packages/coresandbox/src/index.js +++ b/packages/coresandbox/src/index.js @@ -2,6 +2,7 @@ import ListingBlockVariationTeaserContent from './components/Blocks/Listing/List import NewsAndEvents from './components/Views/NewsAndEvents'; import TestBlockView from './components/Blocks/TestBlock/View'; import TestBlockEdit from './components/Blocks/TestBlock/Edit'; +import { SliderSchema as TestBlockSchema } from './components/Blocks/TestBlock/schema'; import codeSVG from '@plone/volto/icons/code.svg'; const testBlock = { @@ -31,6 +32,59 @@ const testBlock = { extensions: {}, }; +const testBlockDefaultEdit = { + id: 'testBlockDefaultEdit', + title: 'testBlockDefaultEdit', + icon: codeSVG, + group: 'common', + view: TestBlockView, + blockSchema: TestBlockSchema, + restricted: false, + mostUsed: true, + sidebarTab: 1, + security: { + addPermission: [], + view: [], + }, + variations: [ + { + id: 'default', + title: 'Default', + }, + { + id: 'custom', + title: 'Custom', + }, + ], + extensions: {}, +}; + +const testBlockDefaultView = { + id: 'testBlockDefaultView', + title: 'testBlockDefaultView', + icon: codeSVG, + group: 'common', + blockSchema: TestBlockSchema, + restricted: false, + mostUsed: true, + sidebarTab: 1, + security: { + addPermission: [], + view: [], + }, + variations: [ + { + id: 'default', + title: 'Default', + }, + { + id: 'custom', + title: 'Custom', + }, + ], + extensions: {}, +}; + const listing = (config) => { return { ...config.blocks.blocksConfig.listing, @@ -68,6 +122,8 @@ export const workingCopyFixture = (config) => { const applyConfig = (config) => { config.blocks.blocksConfig.testBlock = testBlock; + config.blocks.blocksConfig.testBlockDefaultEdit = testBlockDefaultEdit; + config.blocks.blocksConfig.testBlockDefaultView = testBlockDefaultView; config.blocks.blocksConfig.listing = listing(config); config.views.contentTypesViews.Folder = NewsAndEvents; diff --git a/src/components/index.js b/src/components/index.js index e8b46a58f3..eec24b73a0 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -30,6 +30,7 @@ export EventDetails from '@plone/volto/components/theme/EventDetails/EventDetail export PreviewImage from '@plone/volto/components/theme/PreviewImage/PreviewImage'; export Error from '@plone/volto/components/theme/Error/Error'; +export ErrorBoundary from '@plone/volto/components/theme/Error/ErrorBoundary'; export NotFound from '@plone/volto/components/theme/NotFound/NotFound'; export Forbidden from '@plone/volto/components/theme/Forbidden/Forbidden'; export Unauthorized from '@plone/volto/components/theme/Unauthorized/Unauthorized'; @@ -172,6 +173,7 @@ export ObjectBrowserWidgetMode from '@plone/volto/components/manage/Widgets/Obje export ObjectWidget from '@plone/volto/components/manage/Widgets/ObjectWidget'; export ObjectListWidget from '@plone/volto/components/manage/Widgets/ObjectListWidget'; +export EditDefaultBlock from '@plone/volto/components/manage/Blocks/Block/DefaultEdit'; export EditDescriptionBlock from '@plone/volto/components/manage/Blocks/Description/Edit'; export EditTitleBlock from '@plone/volto/components/manage/Blocks/Title/Edit'; export EditToCBlock from '@plone/volto/components/manage/Blocks/ToC/Edit'; @@ -185,6 +187,7 @@ export ViewHeroImageLeftBlock from '@plone/volto/components/manage/Blocks/HeroIm export EditMapBlock from '@plone/volto/components/manage/Blocks/Maps/Edit'; export EditHTMLBlock from '@plone/volto/components/manage/Blocks/HTML/Edit'; +export ViewDefaultBlock from '@plone/volto/components/manage/Blocks/Block/DefaultView'; export ViewDescriptionBlock from '@plone/volto/components/manage/Blocks/Description/View'; export ViewTitleBlock from '@plone/volto/components/manage/Blocks/Title/View'; export ViewToCBlock from '@plone/volto/components/manage/Blocks/ToC/View'; diff --git a/src/components/manage/Blocks/Block/DefaultEdit.jsx b/src/components/manage/Blocks/Block/DefaultEdit.jsx new file mode 100644 index 0000000000..7b736fb551 --- /dev/null +++ b/src/components/manage/Blocks/Block/DefaultEdit.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import config from '@plone/volto/registry'; +import { useIntl } from 'react-intl'; +import { SidebarPortal } from '@plone/volto/components'; +import { BlockDataForm } from '@plone/volto/components'; +import DefaultBlockView from './DefaultView'; + +const DefaultBlockEdit = (props) => { + const { blocksConfig = config.blocks.blocksConfig } = props; + const { data, onChangeBlock, block, selected } = props; + const intl = useIntl(); + const blockSchema = blocksConfig?.[data['@type']]?.blockSchema; + const schema = + typeof blockSchema === 'function' + ? blockSchema({ ...props, intl }) + : blockSchema; + + const BlockView = blocksConfig?.[data['@type']]?.['view'] || DefaultBlockView; + return ( + <> + + {schema ? ( + + { + onChangeBlock(block, { + ...data, + [id]: value, + }); + }} + formData={data} + /> + + ) : ( + '' + )} + + ); +}; + +export default DefaultBlockEdit; diff --git a/src/components/manage/Blocks/Block/DefaultView.jsx b/src/components/manage/Blocks/Block/DefaultView.jsx new file mode 100644 index 0000000000..ceaa3911aa --- /dev/null +++ b/src/components/manage/Blocks/Block/DefaultView.jsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { Container, Segment, Grid, Label } from 'semantic-ui-react'; +import { ErrorBoundary } from '@plone/volto/components'; +import { getWidget } from '@plone/volto/helpers/Widget/utils'; +import config from '@plone/volto/registry'; + +const messages = defineMessages({ + unknownBlock: { + id: 'Unknown Block', + defaultMessage: 'Unknown Block {block}', + }, + invalidBlock: { + id: 'Invalid Block', + defaultMessage: 'Invalid block - Will be removed on saving', + }, +}); + +const DefaultBlockView = (props) => { + const { data, block } = props; + const intl = useIntl(); + const { views } = config.widgets; + const { blocksConfig = config.blocks.blocksConfig } = props; + if (!data) + return
{intl.formatMessage(messages.invalidBlock)}
; + // Compatibility with RenderBlocks non-view + + const blockSchema = blocksConfig?.[data['@type']]?.blockSchema; + const schema = + typeof blockSchema === 'function' + ? blockSchema({ ...props, intl }) + : blockSchema; + const fieldsets = schema.fieldsets || []; + + return schema ? ( + + {fieldsets?.map((fs) => { + return ( +
+ {fs.id !== 'default' &&

{fs.title}

} + {fs.fields?.map((f, key) => { + let field = { + ...schema?.properties[f], + id: f, + widget: getWidget(f, schema?.properties[f]), + }; + let Widget = views?.getWidget(field); + return f !== 'title' ? ( + + + + + + + + + + + + + ) : ( + + ); + })} +
+ ); + })} +
+ ) : ( +
+ {intl.formatMessage(messages.unknownBlock, { + block: data['@type'], + })} +
+ ); +}; + +export default DefaultBlockView; diff --git a/src/components/manage/Blocks/Block/Edit.jsx b/src/components/manage/Blocks/Block/Edit.jsx index 0c71bd603b..4976d4ad7f 100644 --- a/src/components/manage/Blocks/Block/Edit.jsx +++ b/src/components/manage/Blocks/Block/Edit.jsx @@ -13,6 +13,7 @@ import { setSidebarTab } from '@plone/volto/actions'; import config from '@plone/volto/registry'; import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser'; import { applyBlockDefaults } from '@plone/volto/helpers'; +import { ViewDefaultBlock, EditDefaultBlock } from '@plone/volto/components'; import { SidebarPortal, @@ -120,12 +121,12 @@ export class Edit extends Component { const disableNewBlocks = this.props.data?.disableNewBlocks; - let Block = blocksConfig?.[type]?.['edit'] || null; + let Block = blocksConfig?.[type]?.['edit'] || EditDefaultBlock; if ( this.props.data?.readOnly || (!editable && !config.blocks.showEditBlocksInBabelView) ) { - Block = blocksConfig?.[type]?.['view'] || null; + Block = blocksConfig?.[type]?.['view'] || ViewDefaultBlock; } const schema = blocksConfig?.[type]?.['schema'] || BlockSettingsSchema; const blockHasOwnFocusManagement = diff --git a/src/components/theme/Error/ErrorBoundary.jsx b/src/components/theme/Error/ErrorBoundary.jsx new file mode 100644 index 0000000000..516ebf9e03 --- /dev/null +++ b/src/components/theme/Error/ErrorBoundary.jsx @@ -0,0 +1,29 @@ +import React from 'react'; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { hasError: true }; + } + + componentDidCatch(error, errorInfo) { + // eslint-disable-next-line + console.error(error, errorInfo); + } + + render() { + if (this.state.hasError) { + // You can render any custom fallback UI + return
{``}
; + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src/components/theme/Error/ErrorBoundary.test.jsx b/src/components/theme/Error/ErrorBoundary.test.jsx new file mode 100644 index 0000000000..8e76d5184f --- /dev/null +++ b/src/components/theme/Error/ErrorBoundary.test.jsx @@ -0,0 +1,34 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import configureStore from 'redux-mock-store'; +import { Provider } from 'react-intl-redux'; +import { MemoryRouter } from 'react-router-dom'; +import ErrorBoundary from './ErrorBoundary'; + +const mockStore = configureStore(); + +describe('Error boundary', () => { + it('renders an Error', () => { + const store = mockStore({ + intl: { + locale: 'en', + messages: {}, + }, + }); + const ThrowError = () => { + throw new Error('Test'); + }; + + const component = renderer.create( + + + + + + + , + ); + const json = component.toJSON(); + expect(json).toMatchSnapshot(); + }); +}); diff --git a/src/components/theme/Error/__snapshots__/ErrorBoundary.test.jsx.snap b/src/components/theme/Error/__snapshots__/ErrorBoundary.test.jsx.snap new file mode 100644 index 0000000000..e0f6de07d4 --- /dev/null +++ b/src/components/theme/Error/__snapshots__/ErrorBoundary.test.jsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Error boundary renders an Error 1`] = ` +
+  <error: test>
+
+`; diff --git a/src/components/theme/View/RenderBlocks.jsx b/src/components/theme/View/RenderBlocks.jsx index f30f1cc3ce..86de669b25 100644 --- a/src/components/theme/View/RenderBlocks.jsx +++ b/src/components/theme/View/RenderBlocks.jsx @@ -9,6 +9,7 @@ import { } from '@plone/volto/helpers'; import StyleWrapper from '@plone/volto/components/manage/Blocks/Block/StyleWrapper'; import config from '@plone/volto/registry'; +import { ViewDefaultBlock } from '@plone/volto/components'; const messages = defineMessages({ unknownBlock: { @@ -32,7 +33,8 @@ const RenderBlocks = (props) => { {map(content[blocksLayoutFieldname].items, (block) => { const Block = - blocksConfig[content[blocksFieldname]?.[block]?.['@type']]?.view; + blocksConfig[content[blocksFieldname]?.[block]?.['@type']]?.view || + ViewDefaultBlock; const blockData = applyBlockDefaults({ data: content[blocksFieldname][block], From 4e619b73bda70c4b0e52a2fb48048bc11e6f0484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Re=C3=A9?= Date: Fri, 25 Nov 2022 10:15:33 +0100 Subject: [PATCH 082/326] Add tooltip to multivalue labels in select facet (#3976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Víctor Fernández de Alba --- CHANGELOG.md | 1 + src/components/manage/Blocks/Search/components/SelectFacet.jsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d929c88de..15d9d5a7e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Internationalization of descriptions of user add form fields. @wesleybl - Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim - Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh +- Add tooltip to multivalue labels in select facet @reebalazs - Provide a default View/Edit component for blocks @avoinea, @tiberiuichim ### Bugfix diff --git a/src/components/manage/Blocks/Search/components/SelectFacet.jsx b/src/components/manage/Blocks/Search/components/SelectFacet.jsx index a5131f09cc..326829d2a7 100644 --- a/src/components/manage/Blocks/Search/components/SelectFacet.jsx +++ b/src/components/manage/Blocks/Search/components/SelectFacet.jsx @@ -3,6 +3,7 @@ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import { Option, DropdownIndicator, + MultiValueContainer, } from '@plone/volto/components/manage/Widgets/SelectStyling'; import { selectTheme, customSelectStyles } from './SelectStyling'; import { @@ -32,7 +33,7 @@ const SelectFacet = (props) => { options={choices} styles={customSelectStyles} theme={selectTheme} - components={{ DropdownIndicator, Option }} + components={{ DropdownIndicator, Option, MultiValueContainer }} isDisabled={isEditMode} onChange={(data) => { if (data) { From 5f9066a70b9f3b60d462fc96a1aa7027ff9bbac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Fri, 25 Nov 2022 20:10:17 +0100 Subject: [PATCH 083/326] Add contributing branch policy information (#3934) Co-authored-by: Steve Piercy --- CHANGELOG.md | 1 + .../developer-guidelines/branch-policy.md | 44 +++++++++++++++++++ .../developer-guidelines/contributing.md | 5 +++ 3 files changed, 50 insertions(+) create mode 100644 docs/source/developer-guidelines/branch-policy.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d9d5a7e7..1e536dd696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -413,6 +413,7 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa - Remove `sentryOptions` from settings reference. Clean up `deploying/sentry.md`. @stevepiercy - Tidy up `upgrade-guide/index.md`. @stevepiercy - Fix some MyST syntax and English grammar. @stevepiercy +- Add contributing branch policy information @sneridagh @stevepiercy ## 16.0.0-rc.1 (2022-11-18) diff --git a/docs/source/developer-guidelines/branch-policy.md b/docs/source/developer-guidelines/branch-policy.md new file mode 100644 index 0000000000..ebd0f931d6 --- /dev/null +++ b/docs/source/developer-guidelines/branch-policy.md @@ -0,0 +1,44 @@ +The Volto team enforces the following branch policy when developers contribute to its core. + +Releases of general packages (`@plone/generator-volto`, `@plone/scripts`, and so on) are cut from the `master` branch. + +stable and latest +: The terms _stable_ and _latest_ mean the same thing in this policy. + They refer to the stable and latest released version of Volto. + They have no branch names in git. + +canary +: The term _canary_ refers to the metaphorical canary in a coalmine; if an issue is detected following its release, the damage is limited to only those users who have installed it. + It usually includes experimental and breaking features for testing. + During the development process, a canary release will be cut from the `master` branch. + When it becomes worthy of a beta or release candidate version, a new numbered branch should be cut, and non-breaking changes must be merged into it. + +legacy +: A version that it is unsupported and receives no bug fixes. + It has no branch name in git. + +`master` +: This is the bleeding edge branch in git. + It is the branch upon which future development occurs, and from which future releases shall be cut. + + When we cut a release candidate, we: + + 1. create a new numbered git branch from master, and + 2. cut a release candidate version whose name aligns with the new numbered git branch. + + For example, when we cut the release candidate version 16.0.0-rc.1, we created a git branch `16.x.x`. + We also freeze the release candidate, and stop adding features to it. + This allows us to continue development on `master`, which may include both breaking changes that must not be backported, and bug fixes and feature additions that may be backported but only after the release candidate becomes final. + + When opening a pull request, the contributor must open it against `master`. + If the pull request is a feature or a bugfix, and if the release manager deems it useful to the latest version's branch, they may ask the contributor to backport it to that branch. + +`16.x.x` +: This is the current actively developed branch in git, meaning that it may receive new features and bug fixes. + Its version is currently at 16.0.0-rc.1 as a release candidate. + It will become the stable version upon the final release of version 16.0.0. + +`15.x.x` +: At the moment of this writing, `15.x.x` is the current stable branch in git. + Upon the final release of version 16.0.0, the `15.x.x` branch line will become legacy. + diff --git a/docs/source/developer-guidelines/contributing.md b/docs/source/developer-guidelines/contributing.md index 5234987ffd..b3f571fe05 100644 --- a/docs/source/developer-guidelines/contributing.md +++ b/docs/source/developer-guidelines/contributing.md @@ -28,6 +28,11 @@ In your report, please specify a few things: - Which Plone version are you using? - Include relevant screenshots, error messages, and stack traces. +## Branch policy + +```{include} ./branch-policy.md +``` + ## Create a pull request You must sign the [Plone Contributor Agreement](https://plone.org/foundation/contributors-agreement) to contribute code and documentation to any Plone project. From d94fd8956c6dd38a821e5660718265c24706a93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sat, 26 Nov 2022 18:27:12 +0100 Subject: [PATCH 084/326] Sync Changelog with 16.2.0 (#3984) --- CHANGELOG.md | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e536dd696..4ea9239b05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,44 @@ # Change Log -## 16.0.1 (unreleased) +## 16.2.1 (unreleased) ### Breaking ### Feature +### Bugfix + +### Internal + +### Documentation + +## 16.2.0 (2022-11-25) + +### Feature + - Internationalization of descriptions of user add form fields. @wesleybl -- Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim -- Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh - Add tooltip to multivalue labels in select facet @reebalazs - Provide a default View/Edit component for blocks @avoinea, @tiberiuichim ### Bugfix -- Fix jest moduleNameMapper for `@plone/volto/babel` @tiberiuichim -- Fix addons loader test @tiberiuichim -- Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh -- Fix user search by full name in users control panel @reebalazs +- Improve collapsing of whitespace when pasting to slate text block @tiberiuichim - Avoid warning for missing value in NumberWidget @tiberiuichim - Fix crash in Slate link editing in a dexterity field @tiberiuichim -### Internal +## 16.1.0 (2022-11-23) -### Documentation +### Feature + +- Support for drilled down current state and updater function from schema in `ObjectListWidget`. This allows to sync the current object selected from the UI and the block settings and viceversa @sneridagh +- Allow custom style wrapper classnames via fieldname suffixes. Added `config.settings.styleClassNameConverters` to register new suffix converters @tiberiuichim + +### Bugfix + +- Fix jest moduleNameMapper for `@plone/volto/babel` @tiberiuichim +- Fix addons loader test @tiberiuichim +- Pass down `onChangeBlock` prop to all stock blocks in core @sneridagh +- Fix user search by full name in users control panel @reebalazs ## 16.0.0 (2022-11-22) From 549922fd083028ca5daef7128669b87c0054795e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 26 Nov 2022 14:01:16 -0800 Subject: [PATCH 085/326] Rewrite "Upgraded core to use Cypress 11" section (#3986) --- CHANGELOG.md | 2 ++ docs/source/upgrade-guide/index.md | 17 ++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ea9239b05..a3843e7ca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### Documentation +- Rewrite "Upgraded core to use Cypress 11" section. Fixes https://github.com/plone/volto/issues/3979. @stevepiercy + ## 16.2.0 (2022-11-25) ### Feature diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 80919b8419..e80d305004 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -347,27 +347,26 @@ If you need to format dates in Volto, it's recommended to use the `FormattedDate It uses modern recommendations for date formatting on the web. ```` -### Upgraded core to use Cypress 10 +### Upgraded core to use Cypress 11 -Cypress has overhauled the testing app and included native support of Apple Silicon Computers beginning with version 10.2.0. -This dramatically improves the launch and test times in these machines. +Cypress has overhauled their testing application, beginning with Cypress 10. +It now includes native support of Apple silicon chip computers. +This dramatically improved the launch and test times on those machines. It also includes the new "component" testing feature that might be appealing in the near future. + The only drawback is that they also overhauled the configuration, forcing migration from old configuration based on JSON files to a better JavaScript-based one. They also changed and renamed some options. Luckily, Cypress provides both good reporting when an old configuration is in place, and an interactive migration wizard. -Core configuration has been updated, but you will need to update your Cypress configuration if you want to use core's Cypress 10. +Core configuration has been updated to use Cypress 11. +You will need to update your Cypress configuration from versions older than Cypress 10 if you want to use core's Cypress 11. +If you have already updated your configuration to use Cypress 10 or later in a previous upgrade, your configuration might already work with Cypress 11. It is possible that forcing your project to use older versions might still work with old configurations. ```{seealso} See https://docs.cypress.io/guides/references/migration-guide#Migrating-to-Cypress-version-10-0 for more information. ``` -```{note} -Later on, the core has been upgraded to Cypress 11. -However no changes need to be made if you already upgraded to Cypress 10. -``` - ### The complete configuration registry is passed to the add-ons and the project configuration pipeline The core in versions prior to Volto 16.0.0-alpha.22 passed a simplified version of the configuration registry—in fact, a plain object primitive—to the add-on list and project configuration pipeline. From 53c6f75056c0147dd8f2310fa05c16b3828527fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 27 Nov 2022 13:58:25 +0100 Subject: [PATCH 086/326] Towncrier support in Volto (#3985) Co-authored-by: Steve Piercy --- CHANGELOG.md | 16 ++-- README.md | 31 -------- RELEASING.md | 73 +++++++++++++++++++ .../developer-guidelines/contributing.md | 11 ++- news/.gitkeep | 0 news/3985.feature | 1 + package.json | 7 +- .../templates/towncrier_template.jinja | 10 +++ towncrier.toml | 33 +++++++++ 9 files changed, 135 insertions(+), 47 deletions(-) create mode 100644 RELEASING.md create mode 100644 news/.gitkeep create mode 100644 news/3985.feature create mode 100644 packages/scripts/templates/towncrier_template.jinja create mode 100644 towncrier.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index a3843e7ca7..d2592db64a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,12 @@ # Change Log -## 16.2.1 (unreleased) + -### Breaking - -### Feature - -### Bugfix - -### Internal - -### Documentation + - Rewrite "Upgraded core to use Cypress 11" section. Fixes https://github.com/plone/volto/issues/3979. @stevepiercy diff --git a/README.md b/README.md index 7af56783d5..025642ca10 100644 --- a/README.md +++ b/README.md @@ -314,37 +314,6 @@ Browse to [http://localhost:3000](http://localhost:3000) in your browser. yarn test ``` -### Releasing - -For ease the release process, we use `release-it` utility that helps with the process. - -https://www.npmjs.com/package/release-it - -For using it and start a release you need to fulfill the requirements: - -- Have permissions to push on master branch -- Have permissions on the @plone org on npmjs.com -- Have a environment variable (`GITHUB_TOKEN`) with a GitHub personal token with permissions to - write the Volto Release page on GitHub (https://www.npmjs.com/package/release-it#github-releases) - -Then the command for release: - -```shell -yarn release -``` - -a dry-release command for testing the output is also available: - -```shell -yarn dry-release -``` - -and alpha release can also be cut using: - -```shell -yarn release-alpha -``` - ## Acceptance testing Here you can find a guide on how acceptance testing is done in Volto: diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000000..6e5b7cbd04 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,73 @@ +# Releasing + +To ease the release process, we use the utility [`release-it`](https://www.npmjs.com/package/release-it) which helps with the process, and [`towncrier`](https://towncrier.readthedocs.io) for creating and maintaining change logs. + +## Release requirements + +To start a release, you must fulfill the following requirements: + +- Have permission to push to `master` branch +- Have permission on the [`@plone` organization on npm](https://www.npmjs.com/org/plone). +- Have an environment variable `GITHUB_TOKEN` with a GitHub personal token with permissions to write to the [Volto Release page on GitHub](https://github.com/plone/volto/releases). +- Install [`pipx`](https://pypa.github.io/pipx/) in your system. + +To request these permissions, on GitHub tag `@plone/release-team`, or in Discord post to the [`release-team` channel](https://discord.com/channels/786421998426521600/897549410521714760). + +### Permission to push to `master` branch + +The release process involves pushing directly to the `master` branch. +Volto's `master` branch is protected, so the releaser needs to have permission for pushing to it. +At the moment of this writing, members of the GitHub group `@plone/volto-team` have permission to push to `master`. + +### Permission to release Volto to npm registry + +We push Volto's releases to the npm registry. +The releaser has to have permissions for pushing a release in the [`@plone` organization on npm](https://www.npmjs.com/org/plone). +Only the current Owners of this organization can grant permissions to the releaser. + +### Have a GitHub personal token with permissions to write the Volto's Releases + +The `release-it` library takes care of creating and pushing a GitHub Release for each release. +It requires you to get a GitHub personal token with permission to write to the Volto's Releases. +This can be acquired in your GitHub profile page. +When making a release, export the environment variable `GITHUB_TOKEN` in your shell session. + +```shell +export GITHUB_TOKEN="my_looooong_github_token" +``` + +See `release-it` documentation of [GitHub releases](https://www.npmjs.com/package/release-it#github-releases) and GitHub documentation [About releases](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases). + +### Install `pipx` in your system + +The release process calls `towncrier`. +It is a Python library that uses the Python utility `pipx`. +This utility allows you to call and execute Python modules without installing them as a prerequisite in your system. +It works similar to the NodeJS `npx` utility. +On macOS, you can install `pipx` into your system: + +```shell +brew install pipx +``` + +Or follow detailed instructions in the `pipx` documentation for [Installation](https://pypa.github.io/pipx/installation/). + +## Running the release process + +These are the commands to make a Volto release: + +```shell +yarn release +``` + +A dry-release command for testing the output is also available: + +```shell +yarn dry-release +``` + +An alpha release can be cut using: + +```shell +yarn release-alpha +``` diff --git a/docs/source/developer-guidelines/contributing.md b/docs/source/developer-guidelines/contributing.md index b3f571fe05..42568ce6e6 100644 --- a/docs/source/developer-guidelines/contributing.md +++ b/docs/source/developer-guidelines/contributing.md @@ -38,8 +38,15 @@ In your report, please specify a few things: You must sign the [Plone Contributor Agreement](https://plone.org/foundation/contributors-agreement) to contribute code and documentation to any Plone project. This means that we can NOT accept pull requests from you until you do this. -All pull requests must include a note under the `(unreleased)` version under the appropriate subheading of [CHANGELOG.md](https://github.com/plone/volto/blob/master/CHANGELOG.md). -Do not edit already released versions. +All pull requests must include a `towncrier` news item. +This is a file that is placed in the root of the repository directory at `/news`. +Its format must be `###.type`, where `###` is the referenced GitHub issue or pull request number, `.` is the literal extension delimiter, and `type` is one of the following strings. + +- `breaking` for breaking changes +- `bugfix` for bug fixes +- `documentation` for documentation +- `feature` for new features +- `internal` for internal changes If the feature includes a breaking change, you must include instructions for how to upgrade in the [upgrade guide](../upgrade-guide/index.md). diff --git a/news/.gitkeep b/news/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/news/3985.feature b/news/3985.feature new file mode 100644 index 0000000000..8807777031 --- /dev/null +++ b/news/3985.feature @@ -0,0 +1 @@ +Add towncrier support. Create RELEASING.md and move and update Releasing section from README.md into it. @sneridagh @stevepiercy diff --git a/package.json b/package.json index c0c3ed90f1..1a826e5c37 100644 --- a/package.json +++ b/package.json @@ -183,18 +183,17 @@ "yarn i18n", "git add locales" ], - "after:bump": "node changelogupdater.js bump ${version}", - "after:release": "node changelogupdater.js back ${version} && git commit -am 'Back to development' && git push" + "after:bump": "pipx run towncrier build --yes --version ${version}" }, "git": { - "changelog": "node changelogupdater.js excerpt", + "changelog": "pipx run towncrier build --draft --yes --version 0.0.0", "requireUpstream": false, "requireCleanWorkingDir": false }, "github": { "release": true, "releaseName": "${version}", - "releaseNotes": "node changelogupdater.js excerpt" + "releaseNotes": "pipx run towncrier build --draft --yes --version ${version}" } }, "lint-staged": { diff --git a/packages/scripts/templates/towncrier_template.jinja b/packages/scripts/templates/towncrier_template.jinja new file mode 100644 index 0000000000..dc0b1214b7 --- /dev/null +++ b/packages/scripts/templates/towncrier_template.jinja @@ -0,0 +1,10 @@ +{% if sections[""] %} +{% for category, val in definitions.items() if category in sections[""] %} +### {{ definitions[category]['name'] }} + +{% for text, values in sections[""][category].items() %} +- {{ text }} {{ values|join(', ') }} +{% endfor %} + +{% endfor %} +{% endif %} diff --git a/towncrier.toml b/towncrier.toml new file mode 100644 index 0000000000..50f4a30ea2 --- /dev/null +++ b/towncrier.toml @@ -0,0 +1,33 @@ +[tool.towncrier] +filename = "CHANGELOG.md" +directory = "news/" +title_format = "## {version} ({project_date})" +underlines = ["", "", ""] +template = "packages/scripts/templates/towncrier_template.jinja" +start_string = "\n" +issue_format = "[#{issue}](https://github.com/plone/volto/issues/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "Feature" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bugfix" +showcontent = true + +[[tool.towncrier.type]] +directory = "internal" +name = "Internal" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true From 28181d28804f80b0c759810e10a2261ed4929938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 27 Nov 2022 22:50:03 +0100 Subject: [PATCH 087/326] Fix GitHub Towncrier release notes (#3989) --- .gitignore | 1 + CHANGELOG.md | 2 -- news/3979.documentation | 1 + news/3989.bugfix | 1 + package.json | 5 +++-- 5 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 news/3979.documentation create mode 100644 news/3989.bugfix diff --git a/.gitignore b/.gitignore index cdbc798738..971b10aee2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ junit.xml eslint.xml yarn-error.log build +.changelog.draft # yarn 3 .pnp.* diff --git a/CHANGELOG.md b/CHANGELOG.md index d2592db64a..6ff2350b51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,6 @@ -- Rewrite "Upgraded core to use Cypress 11" section. Fixes https://github.com/plone/volto/issues/3979. @stevepiercy - ## 16.2.0 (2022-11-25) ### Feature diff --git a/news/3979.documentation b/news/3979.documentation new file mode 100644 index 0000000000..f5965e8fe8 --- /dev/null +++ b/news/3979.documentation @@ -0,0 +1 @@ +Rewrite "Upgraded core to use Cypress 11" section. @stevepiercy diff --git a/news/3989.bugfix b/news/3989.bugfix new file mode 100644 index 0000000000..7e470e22fd --- /dev/null +++ b/news/3989.bugfix @@ -0,0 +1 @@ +Fix GitHub release notes in new Towncrier release config @sneridagh diff --git a/package.json b/package.json index 1a826e5c37..9fa2562fa3 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,8 @@ "yarn i18n", "git add locales" ], - "after:bump": "pipx run towncrier build --yes --version ${version}" + "after:bump": "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft && pipx run towncrier build --yes --version ${version}", + "after:release": "rm .changelog.draft" }, "git": { "changelog": "pipx run towncrier build --draft --yes --version 0.0.0", @@ -193,7 +194,7 @@ "github": { "release": true, "releaseName": "${version}", - "releaseNotes": "pipx run towncrier build --draft --yes --version ${version}" + "releaseNotes": "cat .changelog.draft" } }, "lint-staged": { From eef928fd8be30d0a66c1fdb923bb43ec9757e10a Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 27 Nov 2022 23:48:51 -0800 Subject: [PATCH 088/326] Close the open glossary list (#3996) --- docs/source/configuration/settings-reference.md | 3 ++- news/3995.documentation | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 news/3995.documentation diff --git a/docs/source/configuration/settings-reference.md b/docs/source/configuration/settings-reference.md index 2aa64b3f58..f451ed516e 100644 --- a/docs/source/configuration/settings-reference.md +++ b/docs/source/configuration/settings-reference.md @@ -232,6 +232,8 @@ styleClassNameConverters data to actual class names. You can customize the generated classname by registering fieldnames with names such as `:`, where the converter is registered here. +``` + ## Views settings @@ -303,5 +305,4 @@ extractScripts For the moment it admits only one property: `errorPages` whose value is a Boolean. If `extractScripts.errorPages` is `true`, the JS will be inserted into the error page. - ``` diff --git a/news/3995.documentation b/news/3995.documentation new file mode 100644 index 0000000000..51ddde3225 --- /dev/null +++ b/news/3995.documentation @@ -0,0 +1 @@ +Close the open Glossary list. @stevepiercy From 086baf97de4622d1cecef8fdd8d8569c7db79f79 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Mon, 28 Nov 2022 08:57:12 -0800 Subject: [PATCH 089/326] =?UTF-8?q?Include=20`CHANGELOG.md`=20at=20the=20c?= =?UTF-8?q?orrect=20path,=20depending=20on=20context=20of=20e=E2=80=A6=20(?= =?UTF-8?q?#3994)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- docs/source/conf.py | 4 ++++ docs/source/index.md | 1 + docs/source/release-notes/index.md | 22 ++++++++++++++++++++++ news/3992.documentation | 1 + 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 docs/source/release-notes/index.md create mode 100644 news/3992.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff2350b51..363031e6b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4500,7 +4500,7 @@ https://docs.voltocms.com/upgrade-guide/ - Added item type as a tooltip in contents @nzambello - Added Italian translations and translated array, token and select widget. @giuliaghisini - Added uploading image preview in FileWidget @iFlameing -- Allow custom express middleware declared with `settings.expressMiddleware`. See [Customizing Express](docs/source/customizing/express.md) @tiberiuichim +- Allow custom express middleware declared with `settings.expressMiddleware`. See [Custom Express middleware](https://6.dev-docs.plone.org/volto/recipes/express.html) @tiberiuichim ### Bugfix diff --git a/docs/source/conf.py b/docs/source/conf.py index d5a274aaca..86f0ff7151 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -56,6 +56,7 @@ extensions = [ "myst_parser", "sphinx.ext.autodoc", + "sphinx.ext.ifconfig", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx_copybutton", @@ -246,3 +247,6 @@ # The name of an image file (relative to this directory) to place at the top of # the title page. latex_logo = "_static/logo_2x.png" + +def setup(app): + app.add_config_value("context", "volto", "env") diff --git a/docs/source/index.md b/docs/source/index.md index ca9c536ecd..ec1b7e40e6 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -48,4 +48,5 @@ backend/index deploying/index upgrade-guide/index developer-guidelines/index +release-notes/index ``` diff --git a/docs/source/release-notes/index.md b/docs/source/release-notes/index.md new file mode 100644 index 0000000000..b7ed8c3611 --- /dev/null +++ b/docs/source/release-notes/index.md @@ -0,0 +1,22 @@ +--- +myst: + html_meta: + "description": "Volto Release Notes" + "property=og:description": "Volto Release Notes for Plone content management system" + "property=og:title": "Volto Release Notes" + "keywords": "Volto, Plone, frontend, Release Notes" +--- + +(release-notes-label)= + +# Volto Release Notes + +````{ifconfig} context in ("documentation") +```{include} ../../../submodules/volto/CHANGELOG.md +``` +```` + +````{ifconfig} context in ("volto") +```{include} ../../../CHANGELOG.md +``` +```` diff --git a/news/3992.documentation b/news/3992.documentation new file mode 100644 index 0000000000..804188f62d --- /dev/null +++ b/news/3992.documentation @@ -0,0 +1 @@ +Include `CHANGELOG.md` at the correct path, depending on context of entire Plone 6 documentation or only Volto documentation. @stevepiercy From 6cb3cfab77e9ddc9e32dc29dd45652881db91542 Mon Sep 17 00:00:00 2001 From: Piero Nicolli Date: Mon, 28 Nov 2022 17:57:48 +0100 Subject: [PATCH 090/326] Added docs for proper usage of draftjs for richtext widgets (#4001) --- docs/source/upgrade-guide/index.md | 3 +++ news/4001.documentation | 1 + 2 files changed, 4 insertions(+) create mode 100644 news/4001.documentation diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index e80d305004..72b97ddb3f 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -100,9 +100,12 @@ For projects already using `volto-slate`, take the following steps in your proje You will have to configure your project to continue using `draftJS`, for example, in your `config.js` or in your add-on: ```js +import { WysiwygWidget } from '@plone/volto/components'; + config.settings.defaultBlockType = 'text' config.blocks.blocksConfig.table.restricted = false; config.blocks.blocksConfig.slateTable.restricted = true; +config.widgets.widget.richtext = WysiwygWidget; ``` #### Existing projects using core `draftJS`, opting to start using `slate` without migrating (possible, but not recommended) diff --git a/news/4001.documentation b/news/4001.documentation new file mode 100644 index 0000000000..3eca5c7f89 --- /dev/null +++ b/news/4001.documentation @@ -0,0 +1 @@ +Added docs for proper usage of draftjs for richtext widgets. @pnicolli From 9650b21983cf8df03dcd41dfc9374a31dd85d979 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Sat, 3 Dec 2022 08:08:07 +0100 Subject: [PATCH 091/326] Forgot to rename the folders --- .../addon/templates/cypress/{integration => tests}/.gitkeep | 0 .../app/templates/cypress/{integration => tests}/.gitkeep | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename packages/generator-volto/generators/addon/templates/cypress/{integration => tests}/.gitkeep (100%) rename packages/generator-volto/generators/app/templates/cypress/{integration => tests}/.gitkeep (100%) diff --git a/packages/generator-volto/generators/addon/templates/cypress/integration/.gitkeep b/packages/generator-volto/generators/addon/templates/cypress/tests/.gitkeep similarity index 100% rename from packages/generator-volto/generators/addon/templates/cypress/integration/.gitkeep rename to packages/generator-volto/generators/addon/templates/cypress/tests/.gitkeep diff --git a/packages/generator-volto/generators/app/templates/cypress/integration/.gitkeep b/packages/generator-volto/generators/app/templates/cypress/tests/.gitkeep similarity index 100% rename from packages/generator-volto/generators/app/templates/cypress/integration/.gitkeep rename to packages/generator-volto/generators/app/templates/cypress/tests/.gitkeep From 6bd9c6caa1b1dbfb9ba663a2d9ca0e0128b85699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sat, 3 Dec 2022 08:48:26 +0100 Subject: [PATCH 092/326] Add cypress config to templates (#4021) --- news/4021.bugfix | 1 + .../generators/addon/templates/cypress.config.js | 8 ++++++++ .../generators/app/templates/cypress.config.js | 8 ++++++++ packages/generator-volto/news/4021.bugfix | 1 + 4 files changed, 18 insertions(+) create mode 100644 news/4021.bugfix create mode 100644 packages/generator-volto/generators/addon/templates/cypress.config.js create mode 100644 packages/generator-volto/generators/app/templates/cypress.config.js create mode 100644 packages/generator-volto/news/4021.bugfix diff --git a/news/4021.bugfix b/news/4021.bugfix new file mode 100644 index 0000000000..5322a06674 --- /dev/null +++ b/news/4021.bugfix @@ -0,0 +1 @@ +Add `cypress.config.js` to generator templates @sneridagh diff --git a/packages/generator-volto/generators/addon/templates/cypress.config.js b/packages/generator-volto/generators/addon/templates/cypress.config.js new file mode 100644 index 0000000000..08d55e62ba --- /dev/null +++ b/packages/generator-volto/generators/addon/templates/cypress.config.js @@ -0,0 +1,8 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + e2e: { + baseUrl: 'http://localhost:3000', + specPattern: 'cypress/tests/**/*.cy.{js,jsx}', + }, +}); diff --git a/packages/generator-volto/generators/app/templates/cypress.config.js b/packages/generator-volto/generators/app/templates/cypress.config.js new file mode 100644 index 0000000000..08d55e62ba --- /dev/null +++ b/packages/generator-volto/generators/app/templates/cypress.config.js @@ -0,0 +1,8 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + e2e: { + baseUrl: 'http://localhost:3000', + specPattern: 'cypress/tests/**/*.cy.{js,jsx}', + }, +}); diff --git a/packages/generator-volto/news/4021.bugfix b/packages/generator-volto/news/4021.bugfix new file mode 100644 index 0000000000..5322a06674 --- /dev/null +++ b/packages/generator-volto/news/4021.bugfix @@ -0,0 +1 @@ +Add `cypress.config.js` to generator templates @sneridagh From e5f9b4a9472e846c98761fc33afa2f6a4da12648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sat, 3 Dec 2022 09:32:59 +0100 Subject: [PATCH 093/326] Remove appExtras dangling in configuration registry (#4024) --- news/4024.internal | 1 + src/config/index.js | 1 - src/registry.js | 8 -------- 3 files changed, 1 insertion(+), 9 deletions(-) create mode 100644 news/4024.internal diff --git a/news/4024.internal b/news/4024.internal new file mode 100644 index 0000000000..85ca8d88cb --- /dev/null +++ b/news/4024.internal @@ -0,0 +1 @@ +Remove unused dangling root appExtras from configuration registry @sneridagh diff --git a/src/config/index.js b/src/config/index.js index a39657a0ce..542737fc21 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -206,7 +206,6 @@ ConfigRegistry.views = config.views; ConfigRegistry.widgets = config.widgets; ConfigRegistry.addonRoutes = config.addonRoutes; ConfigRegistry.addonReducers = config.addonReducers; -ConfigRegistry.appExtras = config.appExtras; ConfigRegistry.components = config.components; applyAddonConfiguration(ConfigRegistry); diff --git a/src/registry.js b/src/registry.js index 3b9674a468..ca0ee5c25f 100644 --- a/src/registry.js +++ b/src/registry.js @@ -74,14 +74,6 @@ class Config { this._data.addonRoutes = addonRoutes; } - get appExtras() { - return this._data.appExtras; - } - - set appExtras(appExtras) { - this._data.appExtras = appExtras; - } - get slots() { return this._data.slots; } From 92f495e796354d52b60a73ad2de6ff08ed0fff5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20S=C3=BCss?= Date: Sat, 3 Dec 2022 09:37:18 +0100 Subject: [PATCH 094/326] =?UTF-8?q?Document=20how=20to=20change=20the=20ba?= =?UTF-8?q?se=20font=20and=20the=20font=20for=20headings.=20Descr=E2=80=A6?= =?UTF-8?q?=20(#4014)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Steve Piercy --- docs/source/theming/custom-styling.md | 61 ++++++++++++++++++++++----- news/4013.documentation | 1 + 2 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 news/4013.documentation diff --git a/docs/source/theming/custom-styling.md b/docs/source/theming/custom-styling.md index 00c90b6481..98e9fbbcb4 100644 --- a/docs/source/theming/custom-styling.md +++ b/docs/source/theming/custom-styling.md @@ -55,8 +55,8 @@ both) files (including the matching folder structure) in your theme folder. ## Pastanaga UI Theme Volto implements Pastanaga UI theme, a new theme for Content Management Systems -created and designed by [Albert Casado](https://twitter.com/albertcasado). For -more info: +created and designed by [Albert Casado](https://twitter.com/albertcasado). +For more info: * https://pastanaga.io * https://github.com/plone/pastanaga @@ -75,28 +75,67 @@ https://github.com/plone/volto/tree/master/theme/themes/pastanaga Pastanaga Theme is an example on how to customize the default Semantic UI look and feel. + ## Examples: Changing Base Font -We start by creating the file `theme/globals/site.variables`. In this file we -can override any value. We do not need to copy the whole file. We can add -variables we would like to change. When we want to change the base font, we add -the following: +We start by creating the file `theme/globals/site.variables`. +In this file we can override any value. +We do not need to copy the whole file from Volto core. +We can add variables we would like to change. +When we want to change the base font, we add the following: ```less @fontName : 'Comic Sans MS'; ``` -> Make sure you have the 'Comic Sans MS' font installed. This is the -> 'ttf-mscorefonts-installer' package for the Debian linux distribution. +The font 'Comic Sans MS' needs to be either installed on you machine or provided with your app. + +To provide the font with your app, the following steps are necessary: + +1. Get your font and copy the files to `/theme/assets/fonts/`. + +1. Usually the font provider gives you ready made font-face instructions. + Copy these font-face code lines to `/theme/typography.css`. + There are a lot of font providers. + If you choose Google fonts, check [google-webfonts-helper](https://gwfh.mranftl.com/fonts) to generate `font-face` CSS code and download font files to include in your project. + +1. Add to the end of `/theme/theme.config` a function to load your font-faces: + + ```less + .loadThemeFonts() { + @import "./typography.css"; + } + ``` + +1. Call this load function in `/theme/globals/site.overrides`: -> If you create a new file, the watcher won't be aware of it, you must restart -> the `yarn start` Volto process again. + ```less + .loadThemeFonts(); + ``` -You can also point it to any Google Web Font name like: +1. In `/theme/globals/site.variables` you can now override both, the font for headings (h1, h2, h3, …) and the font for the rest. + + ```less + // Do not override @fontName! + // @fontName: 'Raleway', 'Helvetica Neue', Arial, Helvetica, sans-serif; + + @pageFont: 'Raleway', 'Helvetica Neue', Arial, Helvetica, sans-serif; + @headerFont: 'Rubic Microbe', 'Helvetica Neue', Arial, Helvetica, sans-serif; + ``` + +Voilà. +Start your Volto app if you created new files. + +````{tip} +For testing purpose you can refrain from installing the font and from providing the font with your app if the font is a Google font. +With the following two lines you tell Volto to load the font "Montserrat" from fonts.google.com. ```less @fontName : 'Montserrat'; +@importGoogleFonts : true; ``` +```` + ## Changing The Breadcrumbs diff --git a/news/4013.documentation b/news/4013.documentation new file mode 100644 index 0000000000..235ea1489b --- /dev/null +++ b/news/4013.documentation @@ -0,0 +1 @@ +Document how to change the base font and the font for headings. Describe how to host the font. @ksuess \ No newline at end of file From 9c233e135282a5808bce2490cb00c0936c4e6d08 Mon Sep 17 00:00:00 2001 From: Alok Kumar Date: Sat, 3 Dec 2022 14:08:00 +0530 Subject: [PATCH 095/326] Fix subscript and supscript active at same time. (#4012) --- news/4011.bugfix | 1 + packages/volto-slate/src/editor/config.jsx | 3 +++ packages/volto-slate/src/utils/blocks.js | 31 ++++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 news/4011.bugfix diff --git a/news/4011.bugfix b/news/4011.bugfix new file mode 100644 index 0000000000..dd0a6594eb --- /dev/null +++ b/news/4011.bugfix @@ -0,0 +1 @@ +Fix subscript and supscript active at same time. @iFlameing \ No newline at end of file diff --git a/packages/volto-slate/src/editor/config.jsx b/packages/volto-slate/src/editor/config.jsx index 6b2a3dd66f..d97bb021d7 100644 --- a/packages/volto-slate/src/editor/config.jsx +++ b/packages/volto-slate/src/editor/config.jsx @@ -338,3 +338,6 @@ export const allowedHeadlineElements = ['em', 'i']; // Scroll into view when typing export const scrollIntoView = true; + +// In inline toolbar only one tag should be active at a time. +export const exclusiveElements = [['sup', 'sub']]; diff --git a/packages/volto-slate/src/utils/blocks.js b/packages/volto-slate/src/utils/blocks.js index e38d80ba60..c225514db3 100644 --- a/packages/volto-slate/src/utils/blocks.js +++ b/packages/volto-slate/src/utils/blocks.js @@ -197,12 +197,43 @@ export const toggleInlineFormat = (editor, format) => { return; } + const exclusiveElements = config.settings.slate.exclusiveElements; + const matchedElements = exclusiveTags(exclusiveElements, format); + let alreadyOneIsActive = + !!matchedElements && + (matchedElements.indexOf(format) === 0 + ? isBlockActive(editor, matchedElements[1]) + : isBlockActive(editor, matchedElements[0])); + + if (alreadyOneIsActive) { + Transforms.unwrapNodes(editor, { + match: (n) => matchedElements.includes(n.type), + split: false, + }); + + const block = { type: format, children: [] }; + Transforms.wrapNodes(editor, block, { split: true }); + return; + } + // `children` property is added automatically as an empty array then // normalized const block = { type: defaultFormat }; Transforms.wrapNodes(editor, block, { split: true }); }; +const exclusiveTags = (exclusiveElements, format) => { + let elements = null; + for (const item of exclusiveElements) { + if (item.includes(format)) { + elements = item; + break; + } + } + + return elements; +}; + export const toggleBlock = (editor, format, allowedChildren) => { // We have 6 boolean variables which need to be accounted for. // See https://docs.google.com/spreadsheets/d/1mVeMuqSTMABV2BhoHPrPAFjn7zUksbNgZ9AQK_dcd3U/edit?usp=sharing From 4c37416f0b3e9a0aa7f1888e1dc22202eb392a92 Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Sat, 3 Dec 2022 05:39:28 -0300 Subject: [PATCH 096/326] Clear error message when canceling user add (#4007) --- news/4006.bugfix | 1 + .../manage/Controlpanels/Users/UsersControlpanel.jsx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 news/4006.bugfix diff --git a/news/4006.bugfix b/news/4006.bugfix new file mode 100644 index 0000000000..c1ccc25204 --- /dev/null +++ b/news/4006.bugfix @@ -0,0 +1 @@ +Clear error message when canceling user add. Fix https://github.com/plone/volto/issues/4006 @wesleybl diff --git a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx index a6c89ba650..b42a5fa74b 100644 --- a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +++ b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx @@ -414,7 +414,9 @@ class UsersControlpanel extends Component { className="modal" onSubmit={this.onAddUserSubmit} submitError={this.state.addUserError} - onCancel={() => this.setState({ showAddUser: false })} + onCancel={() => + this.setState({ showAddUser: false, addUserError: undefined }) + } title={this.props.intl.formatMessage(messages.addUserFormTitle)} loading={this.props.createRequest.loading} schema={{ From 7abef0a3f3f1fd507a04551635ef644cd09c3b87 Mon Sep 17 00:00:00 2001 From: Wesley Barroso Lopes Date: Sat, 3 Dec 2022 05:40:03 -0300 Subject: [PATCH 097/326] Translation of roles in users and groups control panel (#4004) --- news/4002.feature | 1 + .../manage/Controlpanels/Groups/GroupsControlpanel.jsx | 7 +++++-- .../manage/Controlpanels/Users/UsersControlpanel.jsx | 7 +++++-- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 news/4002.feature diff --git a/news/4002.feature b/news/4002.feature new file mode 100644 index 0000000000..b97d9c2d5d --- /dev/null +++ b/news/4002.feature @@ -0,0 +1 @@ +Translation of roles in user and group control panel. Fix https://github.com/plone/volto/issues/4002 @wesleybl diff --git a/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx b/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx index e0bb46cf5c..98ec765654 100644 --- a/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx +++ b/src/components/manage/Controlpanels/Groups/GroupsControlpanel.jsx @@ -449,7 +449,10 @@ class GroupsControlpanel extends Component { messages.addGroupsFormRolesTitle, ), type: 'array', - choices: this.props.roles.map((role) => [role.id, role.id]), + choices: this.props.roles.map((role) => [ + role.id, + role.title, + ]), noValueOption: false, description: '', }, @@ -507,7 +510,7 @@ class GroupsControlpanel extends Component { {this.props.roles.map((role) => ( - {role.id} + {role.title} ))} diff --git a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx index b42a5fa74b..33c070d2a0 100644 --- a/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx +++ b/src/components/manage/Controlpanels/Users/UsersControlpanel.jsx @@ -485,7 +485,10 @@ class UsersControlpanel extends Component { messages.addUserFormRolesTitle, ), type: 'array', - choices: this.props.roles.map((role) => [role.id, role.id]), + choices: this.props.roles.map((role) => [ + role.id, + role.title, + ]), noValueOption: false, }, groups: { @@ -553,7 +556,7 @@ class UsersControlpanel extends Component { {this.props.roles.map((role) => ( - {role.id} + {role.title} ))} From 8afd48993f985b4e67271f6d0bc4c62c8bab59fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sat, 3 Dec 2022 16:05:21 +0100 Subject: [PATCH 098/326] Add changelog to core packages (#4026) --- packages/generator-volto/CHANGELOG.md | 10 ---------- packages/generator-volto/news/.gitkeep | 0 packages/generator-volto/package.json | 8 ++++---- packages/scripts/CHANGELOG.md | 10 ---------- packages/scripts/news/.gitkeep | 0 packages/scripts/package.json | 8 ++++---- 6 files changed, 8 insertions(+), 28 deletions(-) create mode 100644 packages/generator-volto/news/.gitkeep create mode 100644 packages/scripts/news/.gitkeep diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 68a9bc062a..6c3a5e4006 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,15 +1,5 @@ # Change Log -## 6.1.2 (unreleased) - -### Breaking - -### Feature - -### Bugfix - -### Internal - ## 6.1.1 (2022-11-24) ### Bugfix diff --git a/packages/generator-volto/news/.gitkeep b/packages/generator-volto/news/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index 70e476de2b..b0f2ec01cd 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -60,11 +60,11 @@ }, "release-it": { "hooks": { - "after:bump": "node ../scripts/changelogupdater.cjs bump ${version}", - "after:release": "node ../scripts/changelogupdater.cjs back ${version} && git commit -am 'Back to development (generator-volto)' && git push" + "after:bump": "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft && pipx run towncrier build --yes --version ${version}", + "after:release": "rm .changelog.draft" }, "git": { - "changelog": "node ../scripts/changelogupdater.cjs excerpt", + "changelog": "pipx run towncrier build --draft --yes --version 0.0.0", "requireUpstream": false, "requireCleanWorkingDir": false, "commitMessage": "Release generate-volto ${version}", @@ -74,7 +74,7 @@ "github": { "release": true, "releaseName": "@plone/generator-volto ${version}", - "releaseNotes": "node ../scripts/changelogupdater.cjs excerpt" + "releaseNotes": "cat .changelog.draft" } }, "jest": { diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 8c7726ef25..2112481ef8 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,15 +1,5 @@ # Change Log -## 2.2.2 (unreleased) - -### Breaking - -### Feature - -### Bugfix - -### Internal - ## 2.2.1 (2022-11-24) ### Bugfix diff --git a/packages/scripts/news/.gitkeep b/packages/scripts/news/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 6f3311e871..bda3856389 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -40,11 +40,11 @@ }, "release-it": { "hooks": { - "after:bump": "node ./changelogupdater.cjs bump ${version}", - "after:release": "node ./changelogupdater.cjs back ${version} && git commit -am 'Back to development (@plone/scripts)' && git push" + "after:bump": "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft && pipx run towncrier build --yes --version ${version}", + "after:release": "rm .changelog.draft" }, "git": { - "changelog": "node ./changelogupdater.cjs excerpt", + "changelog": "pipx run towncrier build --draft --yes --version 0.0.0", "requireUpstream": false, "requireCleanWorkingDir": false, "commitMessage": "Release @plone/scripts ${version}", @@ -54,7 +54,7 @@ "github": { "release": true, "releaseName": "@plone/scripts ${version}", - "releaseNotes": "node ./changelogupdater.cjs excerpt" + "releaseNotes": "cat .changelog.draft" } }, "publishConfig": { From ce3d0f98c4ea6d36524d991bed3d52d5d47ee870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sat, 3 Dec 2022 16:05:38 +0100 Subject: [PATCH 099/326] Bump Volto core packages with the current Volto version on Volto release (#4025) --- Makefile | 7 +++++++ news/4025.bugfix | 1 + package.json | 2 +- packages/scripts/corepackagebump.js | 20 ++++++++++++++++++++ packages/volto-slate/package.json | 4 ++-- 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 news/4025.bugfix create mode 100644 packages/scripts/corepackagebump.js diff --git a/Makefile b/Makefile index e60efcb688..ef0a7f914a 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ DOCKER_IMAGE=plone/plone-backend:6.0.0rc1 KGS= TESTING_ADDONS=plone.app.robotframework==2.0.0b2 plone.app.testing==7.0.0b2 NODEBIN = ./node_modules/.bin +SCRIPTSPACKAGE = ./packages/scripts # Plone 5 legacy DOCKER_IMAGE5=plone/plone-backend:5.2.9 @@ -161,6 +162,12 @@ docs-test: docs-clean docs-linkcheckbroken docs-spellcheck ## Clean docs build, storybook-build: yarn build-storybook -o docs/_build/storybook +##### Release + +.PHONY: corepackagebump +corepackagebump: + node $(SCRIPTSPACKAGE)/corepackagebump.js packages/volto-slate $(VERSION) + ##### Docker containers .PHONY: start-backend-docker diff --git a/news/4025.bugfix b/news/4025.bugfix new file mode 100644 index 0000000000..113eb5e3b2 --- /dev/null +++ b/news/4025.bugfix @@ -0,0 +1 @@ +Bump Volto core packages with the current Volto version on Volto release @sneridagh diff --git a/package.json b/package.json index 9fa2562fa3..ac98fb312b 100644 --- a/package.json +++ b/package.json @@ -183,7 +183,7 @@ "yarn i18n", "git add locales" ], - "after:bump": "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft && pipx run towncrier build --yes --version ${version}", + "after:bump": "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft && pipx run towncrier build --yes --version ${version} && make corepackagebump VERSION=${version}", "after:release": "rm .changelog.draft" }, "git": { diff --git a/packages/scripts/corepackagebump.js b/packages/scripts/corepackagebump.js new file mode 100644 index 0000000000..ab9a342a50 --- /dev/null +++ b/packages/scripts/corepackagebump.js @@ -0,0 +1,20 @@ +#!/usr/bin/env node +/* eslint no-console: 0 */ +import fs from 'fs'; + +if (process.argv.length < 3) { + console.log(process.argv.length); + process.exit(1); +} + +const pkg = process.argv[2]; +const version = process.argv[3]; + +const packageJSON = JSON.parse(fs.readFileSync(`${pkg}/package.json`, 'utf8')); + +packageJSON.version = version; + +fs.writeFileSync( + `${pkg}/package.json`, + `${JSON.stringify(packageJSON, null, 2)}`, +); diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json index 097607ed95..0152a612bd 100644 --- a/packages/volto-slate/package.json +++ b/packages/volto-slate/package.json @@ -1,6 +1,6 @@ { "name": "@plone/volto-slate", - "version": "5.4.1", + "version": "16.3.1", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", @@ -40,4 +40,4 @@ "lint:fix": "../node_modules/eslint/bin/eslint.js --fix 'src/**/*.{js,jsx}'", "i18n": "mv .i18n.babel.config.js babel.config.js; rm -rf build/messages && NODE_ENV=production node src/i18n.js; mv babel.config.js .i18n.babel.config.js" } -} +} \ No newline at end of file From e9e555aa88635601f1e29d0ead1d91af80134b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 4 Dec 2022 19:02:54 +0100 Subject: [PATCH 100/326] Complete eu translation (#4015) (#4033) Co-authored-by: Mikel Larreategi --- locales/eu/LC_MESSAGES/volto.po | 220 ++++++++++++++++---------------- news/4015.bugfix | 2 + 2 files changed, 112 insertions(+), 110 deletions(-) create mode 100644 news/4015.bugfix diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index f0a6d7901c..6c31f53c97 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -41,12 +41,12 @@ msgstr "Ekintza" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action changed msgid "Action changed" -msgstr "" +msgstr "Akzioa ondo aldatu da" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action: msgid "Action: " -msgstr "" +msgstr "Akzioa:" #: components/manage/Actions/Actions #: components/manage/Contents/Contents @@ -66,12 +66,12 @@ msgstr "Aktibatu eta desaktibatu" #: components/manage/Rules/Rules # defaultMessage: Active msgid "Active" -msgstr "" +msgstr "Aktibo" #: components/manage/Rules/Rules # defaultMessage: Active content rules in this Page msgid "Active content rules in this Page" -msgstr "" +msgstr "Orrialde honetan aktibo dauden eduki-erregelak" #: components/manage/Aliases/Aliases #: components/manage/Controlpanels/Aliases @@ -103,12 +103,12 @@ msgstr "Gehitu edukia" #: components/manage/Controlpanels/Rules/AddRule # defaultMessage: Add Content Rule msgid "Add Content Rule" -msgstr "" +msgstr "Gehitu eduki erregela" #: components/manage/Controlpanels/Rules/AddRule # defaultMessage: Add Rule msgid "Add Rule" -msgstr "" +msgstr "Gehitu erregela" #: components/manage/Toolbar/Types # defaultMessage: Add Translation… @@ -133,7 +133,7 @@ msgstr "Gehitu ordezko helbide berria" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action added msgid "Add action" -msgstr "" +msgstr "Gehitu akzioa" #: components/manage/BlockChooser/BlockChooserButton # defaultMessage: Add block @@ -148,12 +148,12 @@ msgstr "Gehitu blokea..." #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition added msgid "Add condition" -msgstr "" +msgstr "Gehitu baldintza" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Add content rule msgid "Add content rule" -msgstr "" +msgstr "Gehitu eduki erregela" #: components/manage/Widgets/QueryWidget # defaultMessage: Add criteria @@ -238,7 +238,7 @@ msgstr "Gehigarrien ezarpenak" #: components/manage/Rules/Rules # defaultMessage: Added msgid "Added" -msgstr "" +msgstr "Ondo gehitu da" #: components/manage/Widgets/RecurrenceWidget/Occurences # defaultMessage: Additional date @@ -248,32 +248,32 @@ msgstr "Data gehigarria" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon could not be installed msgid "Addon could not be installed" -msgstr "" +msgstr "Produktu gehigarria ezin izan da instalatu" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon could not be uninstalled msgid "Addon could not be uninstalled" -msgstr "" +msgstr "Produktu gehigarria ezin izan da kendu" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon could not be upgraded msgid "Addon could not be upgraded" -msgstr "" +msgstr "Produktu gehigarria ezin izan da eguneratu" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon installed succesfuly msgid "Addon installed succesfuly" -msgstr "" +msgstr "Produktu gehigarria ondo instalatu da" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon uninstalled succesfuly msgid "Addon uninstalled succesfuly" -msgstr "" +msgstr "Produktu gehigarria ondo kendu da" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Addon upgraded succesfuly msgid "Addon upgraded succesfuly" -msgstr "" +msgstr "Produktu gehigarria ondo eguneratu da" #: config/Views # defaultMessage: Album view @@ -355,17 +355,17 @@ msgstr "Ordezko helbidearen bidea → helburu helbidearen bidea (sorrera data et #: components/manage/Rules/Rules # defaultMessage: Applied to subfolders msgid "Applied to subfolders" -msgstr "" +msgstr "Azpikarpetei ondo aplikatu zaie" #: components/manage/Rules/Rules # defaultMessage: Applies to subfolders? msgid "Applies to subfolders?" -msgstr "" +msgstr "Azpikarpetei aplikatu?" #: components/manage/Rules/Rules # defaultMessage: Apply to subfolders msgid "Apply to subfolders" -msgstr "" +msgstr "Azpikarpetei aplikatu" #: components/manage/Toolbar/More # defaultMessage: Apply working copy @@ -391,7 +391,7 @@ msgstr "Goraka" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Assignments msgid "Assignments" -msgstr "" +msgstr "Esleipenak" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Available @@ -401,7 +401,7 @@ msgstr "Aukerak" #: components/manage/Rules/Rules # defaultMessage: Available content rules: msgid "Available content rules:" -msgstr "" +msgstr "Erabilgarri dauden eduki-erregelak:" #: components/manage/Aliases/Aliases #: components/manage/Contents/Contents @@ -481,7 +481,7 @@ msgstr "Defektuz, elementu hau barnean duen karpetaren baimenak heredatu egiten #: components/manage/Contents/Contents # defaultMessage: By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first. msgid "By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first." -msgstr "" +msgstr "Elementu hau ezabatzean, beheko zerrendan dauden loturak apurtuko dira. Hori baldin bada egin nahi duzuna, erreferentziak ezabatzea gomendatzen dizugu." #: components/manage/Controlpanels/DatabaseInformation # defaultMessage: Cache Name @@ -662,32 +662,32 @@ msgstr "Alderatu" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition changed msgid "Condition changed" -msgstr "" +msgstr "Baldintza ondo aldatu da" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition: msgid "Condition: " -msgstr "" +msgstr "Baldintza:" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Configuration Versions msgid "Configuration Versions" -msgstr "" +msgstr "Konfigurazioaren bertsioak" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Configure Content Rule msgid "Configure Content Rule" -msgstr "" +msgstr "Konfiguratu eduki erregela" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Configure Content Rule: {title} msgid "Configure Content Rule: {title}" -msgstr "" +msgstr "Konfiguratu eduki erregela: {title}" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Configure content rule msgid "Configure content rule" -msgstr "" +msgstr "Konfiguratu eduki erregela" #: components/manage/Preferences/ChangePassword #: components/theme/PasswordReset/PasswordReset @@ -718,23 +718,23 @@ msgstr "Edukia" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Content Rule msgid "Content Rule" -msgstr "" +msgstr "Eduki erregela" #: components/manage/Controlpanels/Controlpanels #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Content Rules msgid "Content Rules" -msgstr "" +msgstr "Eduki erregelak" #: components/manage/Rules/Rules # defaultMessage: Content rules for {title} msgid "Content rules for {title}" -msgstr "" +msgstr "{title} elementuaren eduki erregelak" #: components/manage/Rules/Rules # defaultMessage: Content rules from parent folders msgid "Content rules from parent folders" -msgstr "" +msgstr "Gurasoetan dauden eduki erregelak" #: components/manage/Controlpanels/ContentTypes # defaultMessage: Content type created @@ -813,7 +813,7 @@ msgstr "Irizpidea" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Current active configuration msgid "Current active configuration" -msgstr "" +msgstr "Orain aktibo dagoen konfigurazioa" #: components/manage/Blocks/Search/components/FilterList # defaultMessage: Current filters applied @@ -937,7 +937,7 @@ msgstr "Ezabatu erabiltzailea" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Action deleted msgid "Delete action" -msgstr "" +msgstr "Ezabatu akzioa" #: helpers/MessageLabels/MessageLabels # defaultMessage: undefined @@ -952,7 +952,7 @@ msgstr "Ezabatu zutabea" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Condition deleted msgid "Delete condition" -msgstr "" +msgstr "Ezabatu baldintza" #: components/manage/Blocks/Table/Edit # defaultMessage: Delete row @@ -962,7 +962,7 @@ msgstr "Ezabatu errenkada" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Deleted msgid "Deleted" -msgstr "" +msgstr "Ezabatuta" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Depth @@ -999,22 +999,22 @@ msgstr "{title} elementuaren {one} eta {two} errebisioen arteko desberdintasunak #: components/manage/Rules/Rules # defaultMessage: Disable msgid "Disable" -msgstr "" +msgstr "Desaktibatu" #: components/manage/Rules/Rules # defaultMessage: Disable apply to subfolders msgid "Disable apply to subfolders" -msgstr "" +msgstr "Desaktibatu azpikarpetei aplikatzea" #: components/manage/Rules/Rules # defaultMessage: Disabled msgid "Disabled" -msgstr "" +msgstr "Desaktibatuta" #: components/manage/Rules/Rules # defaultMessage: Disabled apply to subfolders msgid "Disabled apply to subfolders" -msgstr "" +msgstr "Azpikarpetei aplikatzea desaktibatuta" #: components/theme/Footer/Footer # defaultMessage: Distributed under the {license}. @@ -1090,7 +1090,7 @@ msgstr "Arrastatu fitxategiak hona..." #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Dry run selected, transaction aborted. msgid "Dry run selected, transaction aborted." -msgstr "" +msgstr "Modu lehorra aukeratu duzunez, transakzioa bertan behera utzi da." #: components/theme/Register/Register # defaultMessage: E-mail @@ -1117,7 +1117,7 @@ msgstr "Editatu" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Edit Rule msgid "Edit Rule" -msgstr "" +msgstr "Aldatu erregela" #: components/theme/Comments/CommentEditModal # defaultMessage: Edit comment @@ -1172,7 +1172,7 @@ msgstr "Objektu zerrenda hutsa" #: components/manage/Rules/Rules # defaultMessage: Enable msgid "Enable" -msgstr "" +msgstr "Aktibatu" #: components/manage/Controlpanels/ContentTypeLayout # defaultMessage: Enable editable Blocks @@ -1182,17 +1182,17 @@ msgstr "Aktibatu editatu daitezkeen blokeak" #: components/manage/Rules/Rules # defaultMessage: Enabled msgid "Enabled" -msgstr "" +msgstr "Aktibatuta" #: components/manage/Rules/Rules # defaultMessage: Enabled here? msgid "Enabled here?" -msgstr "" +msgstr "Hemen aktibatuta?" #: components/manage/Rules/Rules # defaultMessage: Enabled? msgid "Enabled?" -msgstr "" +msgstr "Aktibatuta?" #: components/manage/Blocks/Search/components/DateRangeFacet #: components/manage/Contents/Contents @@ -1280,7 +1280,7 @@ msgstr "Errorea" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Event msgid "Event" -msgstr "" +msgstr "Hitzordua" #: config/Views # defaultMessage: Event listing @@ -1400,7 +1400,7 @@ msgstr "Fitxategi izena" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Filter Rules: msgid "Filter Rules:" -msgstr "" +msgstr "Filtratu erregelak:" #: components/manage/Controlpanels/Aliases # defaultMessage: Filter by prefix @@ -1590,7 +1590,7 @@ msgstr "ID" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: If all of the following conditions are met: msgid "If all of the following conditions are met:" -msgstr "" +msgstr "Baldintza guzti hauek betetzen badira:" #: components/manage/Contents/ContentsPropertiesModal # defaultMessage: If selected, this item will not appear in the navigation tree @@ -1728,7 +1728,7 @@ msgstr "urtean behin" #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" -msgstr "" +msgstr "Bloke okerra" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Item batch size @@ -1780,7 +1780,7 @@ msgstr "Elementuak bakarrak izan behar dira." #: components/manage/Contents/Contents # defaultMessage: Items to be deleted: msgid "Items to be deleted:" -msgstr "" +msgstr "Ezabatuko diren elementuak:" #: components/manage/Blocks/Search/schema # defaultMessage: Label @@ -1820,7 +1820,7 @@ msgstr "Azken aldaketa" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Latest available configuration msgid "Latest available configuration" -msgstr "" +msgstr "Azken konfigurazioa" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Latest version @@ -1913,7 +1913,7 @@ msgstr "Sartu" #: components/theme/Logout/Logout # defaultMessage: Logged out msgid "Logged out" -msgstr "" +msgstr "Irten egin zara" #: components/theme/Login/Login # defaultMessage: Login @@ -2000,7 +2000,7 @@ msgstr "Ertaina" #: helpers/MessageLabels/MessageLabels # defaultMessage: Membership updated msgid "Membership updated" -msgstr "" +msgstr "Kidetza eguneratu da" #: components/theme/ContactForm/ContactForm # defaultMessage: Message @@ -2051,7 +2051,7 @@ msgstr "Gehiago" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. msgid "More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide." -msgstr "" +msgstr "Eguneraketa prozeduraren inguruko informazio gehiago plone.org atariko eguneraketa gidan aurkitu dezakezu." #: config/Views # defaultMessage: Mosaic layout @@ -2061,7 +2061,7 @@ msgstr "Mosaiko bista" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Move down msgid "Move down" -msgstr "" +msgstr "Behera mugitu" #: components/manage/Contents/ContentsItem # defaultMessage: Move to bottom of folder @@ -2076,7 +2076,7 @@ msgstr "Karpetaren gorengo tokira mugitu" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Move up msgid "Move up" -msgstr "" +msgstr "Gora mugitu" #: components/manage/Blocks/Search/schema # defaultMessage: Multiple choices? @@ -2102,7 +2102,7 @@ msgstr "Izena" #: components/manage/Widgets/AlignWidget # defaultMessage: Narrow msgid "Narrow" -msgstr "" +msgstr "Estutu" #: error # defaultMessage: Navigate back @@ -2291,7 +2291,7 @@ msgstr "Ados" #: components/manage/Widgets/IdWidget # defaultMessage: Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed. msgid "Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed." -msgstr "" +msgstr "Letra xeheak (a-z), karaktere berezi gabe, zenbakiak (0-9) eta "-", "_" eta "." ikurrak bakarrik erabili daitezke." #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar @@ -2355,7 +2355,7 @@ msgstr "Itsatsi blokeak" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Perform the following actions: msgid "Perform the following actions:" -msgstr "" +msgstr "Ekintza hauek exekutatu:" #: components/manage/Sharing/Sharing # defaultMessage: Permissions have been updated successfully @@ -2392,12 +2392,12 @@ msgstr "Elementu honen edukia sortzearen arduradunak. Idatzi erabiltzaile-izen b #: components/manage/Controlpanels/Controlpanels # defaultMessage: Please continue with the upgrade. msgid "Please continue with the upgrade." -msgstr "" +msgstr "Jarraitu eguneraketarekin." #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Please ensure you have a backup of your site before performing the upgrade. msgid "Please ensure you have a backup of your site before performing the upgrade." -msgstr "" +msgstr "Ziurtatu webgunearen segurtasun kopia bat egin duzula eguneraketa egin aurretik." #: components/manage/Blocks/Video/Body # defaultMessage: Please enter a valid URL by deleting the block and adding a new video block. @@ -2442,7 +2442,7 @@ msgstr "Plone{reg} Open Source CMS/WCM" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Position changed msgid "Position changed" -msgstr "" +msgstr "Posizioa ondo aldatu da" #: components/manage/Widgets/SchemaWidget # defaultMessage: Possible values (Enter allowed choices one per line). @@ -2452,7 +2452,7 @@ msgstr "Balio posibleak" #: components/manage/Contents/Contents # defaultMessage: Potential link breakage msgid "Potential link breakage" -msgstr "" +msgstr "Loturak apurtzea gerta daiteke" #: components/theme/Footer/Footer # defaultMessage: Powered by Plone & Python @@ -2720,23 +2720,23 @@ msgstr "Erroa" #: components/manage/Controlpanels/Rules/AddRule # defaultMessage: Rule added msgid "Rule added" -msgstr "" +msgstr "Erregela gehitu da" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Rule enable changed msgid "Rule enable changed" -msgstr "" +msgstr "Erregela aktibatzea aldatu da" #: components/manage/Rules/Rules #: components/manage/Toolbar/More # defaultMessage: Rules msgid "Rules" -msgstr "" +msgstr "Erregelak" #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below. msgid "Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below." -msgstr "" +msgstr "Erregelak akzio bat gertatzen denean exekutatzen dira. Erregelaren akzioak erregelaren baldintza guztiak betetzen direnean exekutatuko dira. Akzio eta baldintzak beheko botoiak erabiliz gehitu ditzakezu." #: components/manage/Add/Add #: components/manage/Controlpanels/ContentType @@ -2761,7 +2761,7 @@ msgstr "Gorde errepikapena" #: helpers/MessageLabels/MessageLabels # defaultMessage: Saved msgid "Saved" -msgstr "" +msgstr "Gordeta" #: components/manage/Controlpanels/ContentTypesActions # defaultMessage: Schema @@ -2844,7 +2844,7 @@ msgstr "Bilatu erabiltzaileak..." #: components/manage/Blocks/Search/components/SearchDetails # defaultMessage: Searched for: {searchedtext}. msgid "Searched for: {searchedtext}." -msgstr "" +msgstr "Bilatutakoa: {searchedtext}." #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField # defaultMessage: Second @@ -2916,7 +2916,7 @@ msgstr "Bidali" #: helpers/MessageLabels/MessageLabels # defaultMessage: Send a confirmation mail with a link to set the password. msgid "Send a confirmation mail with a link to set the password." -msgstr "" +msgstr "Bidali pasahitza ezartzeko lotura duen baieztapen mezua." #: components/theme/PasswordReset/PasswordReset # defaultMessage: Set my password @@ -3000,7 +3000,7 @@ msgstr "Ordenazio irizpideak erakutsi?" #: components/manage/Blocks/Search/schema # defaultMessage: Show total results msgid "Show total results" -msgstr "" +msgstr "Emaitza kopurua erakutsi" #: components/manage/Sidebar/Sidebar # defaultMessage: Shrink sidebar @@ -3128,7 +3128,7 @@ msgstr "Egoera" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Status msgid "Status" -msgstr "" +msgstr "Egoera" #: components/manage/Multilingual/CompareLanguages # defaultMessage: Stop compare @@ -3301,12 +3301,12 @@ msgstr "Botoia badago bilaketa ez da idatzi ahal egingo, ENTER sakatzean baizik" #: components/manage/Rules/Rules # defaultMessage: The following content rules are active in this Page. Use the content rules control panel to create new rules or delete or modify existing ones. msgid "The following content rules are active in this Page. Use the content rules control panel to create new rules or delete or modify existing ones." -msgstr "" +msgstr "Orrialde honetan eduki erregela hauek daude aktibo. Erabili erregelen kontrol panela erregela berriak sortu, ezabatu edo daudenak aldatzeko." #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: The following list shows which upgrade steps are going to be run. Upgrading sometimes performs a catalog/security update, which may take a long time on large sites. Be patient. msgid "The following list shows which upgrade steps are going to be run. Upgrading sometimes performs a catalog/security update, which may take a long time on large sites. Be patient." -msgstr "" +msgstr "Zerrenda honek exekutatuko diren eguneraketa pausoak erakusten ditu. Eguneraketan, batzuetan, katalogoaren eguneraketa edo segurtasun eguneraketa bat gertatzen da, eta honek batzuetan denbora asko hartzen du. " #: components/manage/Contents/Contents # defaultMessage: The item could not be deleted. @@ -3332,7 +3332,7 @@ msgstr "Izen-emate prozesua ondo egin duzu. Begiratu zure eposta, kontua aktibat #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: The site configuration is outdated and needs to be upgraded. msgid "The site configuration is outdated and needs to be upgraded." -msgstr "" +msgstr "Atariaren konfigurazioa zaharkituta dago eta eguneratu egin behar da." #: components/manage/Toolbar/More # defaultMessage: The working copy was discarded @@ -3352,7 +3352,7 @@ msgstr "Zerbitzariak konfigurazio arazo bat du" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: There was an error with the upgrade. msgid "There was an error with the upgrade." -msgstr "" +msgstr "Errore bat gertatu da eguneraketan." #: components/manage/Form/InlineForm # defaultMessage: There were some errors @@ -3373,7 +3373,7 @@ msgstr "Hirugarren" #: components/manage/Contents/Contents # defaultMessage: This Page is referenced by the following items: msgid "This Page is referenced by the following items:" -msgstr "" +msgstr "Orrialde honek elementu hauetatik loturak ditu:" #: components/manage/WorkingCopyToastsFactory/WorkingCopyToastsFactory # defaultMessage: This has an ongoing working copy in {title} @@ -3408,7 +3408,7 @@ msgstr "Orrialde hau ez da existitzen..." #: components/manage/Controlpanels/Rules/ConfigureRule # defaultMessage: This rule is assigned to the following locations: msgid "This rule is assigned to the following locations:" -msgstr "" +msgstr "Erregela hau toki hauetan dago esletituta:" #: components/manage/Widgets/DatetimeWidget # defaultMessage: Time @@ -3432,7 +3432,7 @@ msgstr "Izenburua" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Title field error. Value not provided or already existing. msgid "Title field error. Value not provided or already existing." -msgstr "" +msgstr "Errorea izenburuaren eremuan. Ez duzu ezer idatzi edo jada existitzen da." #: components/manage/Controlpanels/DatabaseInformation # defaultMessage: Total active and non-active objects @@ -3499,7 +3499,7 @@ msgstr "Itzulpenaren lotura kendu da" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Triggering event field error. Please select a value msgid "Triggering event field error. Please select a value" -msgstr "" +msgstr "Akzioaren eremuan errorea. Aukeratu balio bat" #: components/manage/Controlpanels/ContentTypes #: components/manage/Widgets/SchemaWidget @@ -3526,7 +3526,7 @@ msgstr "Idatzi testua..." #: components/manage/TextLineEdit/TextLineEdit # defaultMessage: Type the heading… msgid "Type the heading…" -msgstr "" +msgstr "Idatzi izenburua..." #: components/manage/Blocks/Title/Edit # defaultMessage: Type the title… @@ -3554,12 +3554,12 @@ msgstr "{title} orrialdearen URLen kudeaketa" #: components/manage/Rules/Rules # defaultMessage: Unassign msgid "Unassign" -msgstr "" +msgstr "Kendu esleipena" #: components/manage/Rules/Rules # defaultMessage: Unassigned msgid "Unassigned" -msgstr "" +msgstr "Esleitu gabe" #: components/manage/Toolbar/More #: components/theme/Unauthorized/Unauthorized @@ -3639,22 +3639,22 @@ msgstr "Instalatu daitezkeen eguneraketak" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade msgid "Upgrade" -msgstr "" +msgstr "Eguneratu" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Plone Site msgid "Upgrade Plone Site" -msgstr "" +msgstr "Plone ataria eguneratu" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Report msgid "Upgrade Report" -msgstr "" +msgstr "Eguneraketaren txostena" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Steps msgid "Upgrade Steps" -msgstr "" +msgstr "Eguneraketa pausoak" #: components/manage/Contents/Contents # defaultMessage: Upload @@ -3690,12 +3690,12 @@ msgstr "Irudia kargatzen" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Use the form below to define the new content rule msgid "Use the form below to define the new content rule" -msgstr "" +msgstr "Erabili formulario hau erregela berria definitzeko" #: components/manage/Controlpanels/Rules/Rules # defaultMessage: Use the form below to define, change or remove content rules. Rules will automatically perform actions on content when certain triggers take place. After defining rules, you may want to go to a folder to assign them, using the 'rules' item in the actions menu. msgid "Use the form below to define, change or remove content rules. Rules will automatically perform actions on content when certain triggers take place. After defining rules, you may want to go to a folder to assign them, using the 'rules' item in the actions menu." -msgstr "" +msgstr "Erabili formulario hau erregela berriak sortu, aldatu edo ezabatzeko. Erregelek ekintza jakin batzuk gertatzean akzio jakin batzuk exekutatuko dituzte. Erregelak definitu ostean, erregelak zein tokitan esleitu aukeratu beharko duzu akzioen menuko "erregelak" erabiliz." #: components/manage/Widgets/SchemaWidget #: components/manage/Widgets/SelectWidget @@ -3760,7 +3760,7 @@ msgstr "Formulario hau erabiliz, elementu baten ordezko helbideak kudeatu ditzak #: helpers/Extensions/withBlockSchemaEnhancer # defaultMessage: Variation msgid "Variation" -msgstr "Aldakia" +msgstr "Aldaera" #: components/manage/Controlpanels/Controlpanels #: components/manage/Controlpanels/UpgradeControlPanel @@ -3822,7 +3822,7 @@ msgstr "Hiztegiaren terminoak" #: components/manage/Controlpanels/VersionOverview # defaultMessage: You are running in 'debug mode'. This mode is intended for sites that are under development. This allows many configuration changes to be immediately visible, but will make your site run more slowly. To turn off debug mode, stop the server, set 'debug-mode=off' in your buildout.cfg, re-run bin/buildout and then restart the server process. msgid "Warning Regarding debug mode" -msgstr "'Debug moduan' diharduzu. Modu hau garapenean dauden atarientzako da. Honekin aldaketa asko berehala ikusten dira baina zure ataria geldoago joan daiteke. Debug modua kentzeko, gelditu zerbitzaria ezarri "debug-mode=off" aukera zure buildout.cfg fitxategian, exekutatu bin/buildout eta berrabiarazi zerbitzariaren prozesua." +msgstr "'Debug moduan' diharduzu. Modu hau garapenean dauden atarientzako da. Honekin aldaketa asko berehala ikusten dira baina zure ataria geldoago joan daiteke. Debug modua kentzeko, gelditu zerbitzaria ezarri 'debug-mode=off' aukera zure buildout.cfg fitxategian, exekutatu bin/buildout eta berrabiarazi zerbitzariaren prozesua." #: components/theme/ConnectionRefused/ConnectionRefused # defaultMessage: We apologize for the inconvenience, but the backend of the site you are accessing is not available right now. Please, try again later. @@ -3875,19 +3875,19 @@ msgstr "Data hau iristean, elementua ez da erakutsiko bilaketa eta zerrendetan" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Whether or not execution of further rules should stop after this rule is executed msgid "Whether or not execution of further rules should stop after this rule is executed" -msgstr "" +msgstr "Erregela hau exekutatu ostean beste erregelarik exekutatuko den ala ez" #: components/manage/Controlpanels/Rules/AddRule #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops msgid "Whether or not other rules should be triggered by the actions launched by this rule. Activate this only if you are sure this won't create infinite loops" -msgstr "" +msgstr "Erregela honek eragindako akzioek erregela gehiago exekutatuaraziko dituzten ala ez. Aktibatu honek begizta infinituak sortuko ez dituela ziur bazaude." #: components/manage/Controlpanels/Rules/AddRule #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Whether or not the rule is currently enabled msgid "Whether or not the rule is currently enabled" -msgstr "" +msgstr "Erregela aktibatuta dagoen ala ez" #: components/manage/Controlpanels/UndoControlpanel #: components/manage/History/History @@ -3961,7 +3961,7 @@ msgstr "Ezin duzu elementua hemen itsatsi" #: components/theme/Logout/Logout # defaultMessage: You have been logged out from the site. msgid "You have been logged out from the site." -msgstr "" +msgstr "Webgunetik irten egin zara" #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Your email is required for reset your password. @@ -3981,7 +3981,7 @@ msgstr "Zure hizkuntza" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Your site is up to date. msgid "Your site is up to date." -msgstr "" +msgstr "Zure ataria eguneratuta dago." #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Your username is required for reset your password. @@ -3991,27 +3991,27 @@ msgstr "Erabiltzaile-izena derrigorrezkoa da zure pasahitza berrezartzeko." #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. msgid "addUserFormEmailDescription" -msgstr "" +msgstr "Zure pasahitza galtzen baduzu beharrezkoa da. Zure pribatutasuna zaintzen dugu eta ez diogu helbide hau inori emango edo inon argitaratuko." #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter full name, e.g. John Smith. msgid "addUserFormFullnameDescription" -msgstr "" +msgstr "Idatzi zure izen osoa, adb.: Jon Garmendia" #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter your new password. Minimum 8 characters. msgid "addUserFormPasswordDescription" -msgstr "" +msgstr "Idatzi zure pasahitz berria. Gutxienez 8 karaktere." #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. msgid "addUserFormUsernameDescription" -msgstr "" +msgstr "Idatzi zure erabiltzaile izena, "jgarmendia" moduko zerbait. Ez da onartzen espaziorik edo karaktere berezirik. Erabiltzaile izenak eta pasahitzak letra larri eta xeheak bereizten dituzte. Hau da webgunera sartzeko erabiliko den izena." #: components/manage/Blocks/Search/schema # defaultMessage: Available views msgid "availableViews" -msgstr "" +msgstr "Bistak" #: components/theme/Login/Login #: components/theme/PasswordReset/RequestPasswordReset @@ -4022,7 +4022,7 @@ msgstr "Pasahitza ahaztuta?" #: config/Blocks # defaultMessage: Checkbox msgid "checkboxFacet" -msgstr "" +msgstr "Aukeraketa kutxa" #: config/Blocks # defaultMessage: Common @@ -4037,7 +4037,7 @@ msgstr "Konparatu hizkuntza honekin" #: config/Blocks # defaultMessage: Date Range msgid "daterangeFacet" -msgstr "" +msgstr "Data-tartea" #: components/manage/Blocks/Block/EditBlockWrapper # defaultMessage: delete @@ -4246,7 +4246,7 @@ msgstr "besteak" #: components/manage/Contents/ContentsItem # defaultMessage: Pending msgid "pending" -msgstr "" +msgstr "Zain" #: components/manage/Contents/ContentsItem # defaultMessage: Private @@ -4436,12 +4436,12 @@ msgstr "urte" #: config/Blocks # defaultMessage: Select msgid "selectFacet" -msgstr "" +msgstr "Aukeraketa laukia" #: components/manage/Blocks/Search/components/ViewSwitcher # defaultMessage: Select view msgid "selectView" -msgstr "" +msgstr "Aukeratu bista" #: components/theme/SkipLinks/SkipLinks # defaultMessage: Skip to footer @@ -4486,12 +4486,12 @@ msgstr "Edukien taula" #: config/Blocks # defaultMessage: Toggle msgid "toggleFacet" -msgstr "" +msgstr "Aktibatu/Desaktibatu" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Update from version {origin} to {destination} msgid "upgradeVersions" -msgstr "" +msgstr "Eguneratu {origin} bertsiotik {destination} bertsiora" #: helpers/MessageLabels/MessageLabels # defaultMessage: Input must be valid url (www.something.com or http(s)://www.something.com) @@ -4511,7 +4511,7 @@ msgstr "Bideoa" #: components/manage/Blocks/Search/schema # defaultMessage: Views msgid "views" -msgstr "" +msgstr "Bistak" #: components/theme/EventDetails/EventDetails # defaultMessage: Visit external website diff --git a/news/4015.bugfix b/news/4015.bugfix new file mode 100644 index 0000000000..cce3ae5d34 --- /dev/null +++ b/news/4015.bugfix @@ -0,0 +1,2 @@ +Complete eu translation +[erral] From a6db8a845a675e597351bb626630ba2a68f288c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 4 Dec 2022 19:03:59 +0100 Subject: [PATCH 101/326] Complete es translation (#4016) (#4034) Co-authored-by: Mikel Larreategi --- locales/es/LC_MESSAGES/volto.po | 56 ++++++++++++++++----------------- news/4016.bugfix | 2 ++ 2 files changed, 30 insertions(+), 28 deletions(-) create mode 100644 news/4016.bugfix diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index 783599e75c..8c7fc617d0 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: Plone\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-12-03 03:20-0400\n" -"PO-Revision-Date: 2022-10-31 21:42-0400\n" +"PO-Revision-Date: 2022-12-02 11:45+0100\n" "Last-Translator: Leonardo J. Caballero G. \n" "Language: es\n" "Language-Team: ES \n" @@ -15,7 +15,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "MIME-Version: 1.0\n" -"X-Generator: Poedit 2.2.1\n" +"X-Generator: Poedit 2.3\n" "Language-Code: es\n" "Language-Name: Español\n" "Preferred-Encodings: utf-8\n" @@ -676,7 +676,7 @@ msgstr "Condición: " #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Configuration Versions msgid "Configuration Versions" -msgstr "" +msgstr "Versiones de la configuración" #: components/manage/Controlpanels/Rules/EditRule # defaultMessage: Configure Content Rule @@ -817,7 +817,7 @@ msgstr "Criterios" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Current active configuration msgid "Current active configuration" -msgstr "" +msgstr "Configuración activa" #: components/manage/Blocks/Search/components/FilterList # defaultMessage: Current filters applied @@ -1094,7 +1094,7 @@ msgstr "Arrastrar archivos aquí..." #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Dry run selected, transaction aborted. msgid "Dry run selected, transaction aborted." -msgstr "" +msgstr "Se ha seleccionado el modo de prueba, transacción abortada" #: components/theme/Register/Register # defaultMessage: E-mail @@ -1732,7 +1732,7 @@ msgstr "Intervalo anual" #: components/theme/View/RenderBlocks # defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" -msgstr "" +msgstr "Bloque no válido" #: components/manage/Widgets/QuerystringWidget # defaultMessage: Item batch size @@ -1824,7 +1824,7 @@ msgstr "Última modificación" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Latest available configuration msgid "Latest available configuration" -msgstr "" +msgstr "Última configuración disponible" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Latest version @@ -2004,7 +2004,7 @@ msgstr "Mediano" #: helpers/MessageLabels/MessageLabels # defaultMessage: Membership updated msgid "Membership updated" -msgstr "" +msgstr "Pertenencia actualizada" #: components/theme/ContactForm/ContactForm # defaultMessage: Message @@ -2055,7 +2055,7 @@ msgstr "Más" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. msgid "More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide." -msgstr "" +msgstr "Puede encontrar más información sobre el procedimiento de actualización en la sección de documentación de plone.org, en la Guía de Actualización." #: config/Views # defaultMessage: Mosaic layout @@ -2106,7 +2106,7 @@ msgstr "Nombre" #: components/manage/Widgets/AlignWidget # defaultMessage: Narrow msgid "Narrow" -msgstr "" +msgstr "Filtrar" #: error # defaultMessage: Navigate back @@ -2396,12 +2396,12 @@ msgstr "Las personas responsables de la creación del contenido de este elemento #: components/manage/Controlpanels/Controlpanels # defaultMessage: Please continue with the upgrade. msgid "Please continue with the upgrade." -msgstr "" +msgstr "Continúe con la actualización" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Please ensure you have a backup of your site before performing the upgrade. msgid "Please ensure you have a backup of your site before performing the upgrade." -msgstr "" +msgstr "Por favor, asegúrese de que tiene una copia de seguridad de su sitio antes de realizar la actualización." #: components/manage/Blocks/Video/Body # defaultMessage: Please enter a valid URL by deleting the block and adding a new video block. @@ -3310,7 +3310,7 @@ msgstr "Las siguientes reglas de contenido están activas en esta página. Utili #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: The following list shows which upgrade steps are going to be run. Upgrading sometimes performs a catalog/security update, which may take a long time on large sites. Be patient. msgid "The following list shows which upgrade steps are going to be run. Upgrading sometimes performs a catalog/security update, which may take a long time on large sites. Be patient." -msgstr "" +msgstr "La siguiente lista muestra los pasos de actualización que se ejecutarán. Algunos de esos pasos requieren que se ejecute una actualización del catálogo o de seguridad, por lo que puede que tarden mucho en algunos sitios grandes." #: components/manage/Contents/Contents # defaultMessage: The item could not be deleted. @@ -3336,7 +3336,7 @@ msgstr "El registro fue exitoso. Por favor, verifique su bandeja de entrada para #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: The site configuration is outdated and needs to be upgraded. msgid "The site configuration is outdated and needs to be upgraded." -msgstr "" +msgstr "La configuración del sitio está anticuada y debe ser actualizada." #: components/manage/Toolbar/More # defaultMessage: The working copy was discarded @@ -3356,7 +3356,7 @@ msgstr "Hay un error de configuración en el servidor" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: There was an error with the upgrade. msgid "There was an error with the upgrade." -msgstr "" +msgstr "Ha ocurrido un error con la actualización" #: components/manage/Form/InlineForm # defaultMessage: There were some errors @@ -3643,22 +3643,22 @@ msgstr "Actualizaciones disponibles" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade msgid "Upgrade" -msgstr "" +msgstr "Actualizar" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Plone Site msgid "Upgrade Plone Site" -msgstr "" +msgstr "Actualizar este sitio" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Report msgid "Upgrade Report" -msgstr "" +msgstr "Informe de actualización" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Upgrade Steps msgid "Upgrade Steps" -msgstr "" +msgstr "Pasos de actualización" #: components/manage/Contents/Contents # defaultMessage: Upload @@ -3985,7 +3985,7 @@ msgstr "Su idioma preferido" #: components/manage/Controlpanels/UpgradeControlPanel # defaultMessage: Your site is up to date. msgid "Your site is up to date." -msgstr "" +msgstr "Su sitio está actualizado." #: components/theme/PasswordReset/RequestPasswordReset # defaultMessage: Your username is required for reset your password. @@ -3995,22 +3995,22 @@ msgstr "El nombre de usuario es necesario para restablecer su contraseña." #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter an email address. This is necessary in case the password is lost. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. msgid "addUserFormEmailDescription" -msgstr "" +msgstr "Introduzca su dirección de correo. Esto es necesario en caso de pérdida de su contraseña. Respetaremos su privacidad y no divulgaremos su dirección a terceros ni la expondremos en este sitio." #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter full name, e.g. John Smith. msgid "addUserFormFullnameDescription" -msgstr "" +msgstr "Introduzca su nombre completo, por ejemplo José Pérez." #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter your new password. Minimum 8 characters. msgid "addUserFormPasswordDescription" -msgstr "" +msgstr "Introduzca su nueva contraseña. Mínimo 8 caracteres." #: helpers/MessageLabels/MessageLabels # defaultMessage: Enter a user name, usually something like "jsmith". No spaces or special characters. Usernames and passwords are case sensitive, make sure the caps lock key is not enabled. This is the name used to log in. msgid "addUserFormUsernameDescription" -msgstr "" +msgstr "Introduzca el nombre de usuario que desee utilizar. Generalmente algo como "jperez" o "jose_perez". No están permitidos caracteres especiales o espacios en el nombre de usuario. Los nombres de usuario y las contraseñas son sensibles a mayúsculas y minúsculas, asegúrese que la tecla de bloqueo de mayúsculas no está activada ('caps lock'). Este es el nombre que utilizará para identificarse." #: components/manage/Blocks/Search/schema # defaultMessage: Available views @@ -4026,7 +4026,7 @@ msgstr "¿Olvidaste tu contraseña?" #: config/Blocks # defaultMessage: Checkbox msgid "checkboxFacet" -msgstr "" +msgstr "Casilla de verificación" #: config/Blocks # defaultMessage: Common @@ -4041,7 +4041,7 @@ msgstr "Comparar con el idioma" #: config/Blocks # defaultMessage: Date Range msgid "daterangeFacet" -msgstr "" +msgstr "Rango de fechas" #: components/manage/Blocks/Block/EditBlockWrapper # defaultMessage: delete @@ -4440,7 +4440,7 @@ msgstr "años" #: config/Blocks # defaultMessage: Select msgid "selectFacet" -msgstr "" +msgstr "Menú de selección" #: components/manage/Blocks/Search/components/ViewSwitcher # defaultMessage: Select view @@ -4490,7 +4490,7 @@ msgstr "Tabla de contenidos" #: config/Blocks # defaultMessage: Toggle msgid "toggleFacet" -msgstr "" +msgstr "Activar/Desactivar" #: components/manage/Controlpanels/AddonsControlpanel # defaultMessage: Update from version {origin} to {destination} diff --git a/news/4016.bugfix b/news/4016.bugfix new file mode 100644 index 0000000000..60e8634e5f --- /dev/null +++ b/news/4016.bugfix @@ -0,0 +1,2 @@ +Complete es translation +[erral] From cf0ac884d3d52a0fb99e77c4fc38489c73883568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Sun, 4 Dec 2022 19:50:26 +0100 Subject: [PATCH 102/326] Use container component in registry (#4032) --- news/4032.feature | 1 + src/components/theme/View/DefaultView.jsx | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 news/4032.feature diff --git a/news/4032.feature b/news/4032.feature new file mode 100644 index 0000000000..618c744761 --- /dev/null +++ b/news/4032.feature @@ -0,0 +1 @@ +Use the component registry for `Container` component in DefaultView @sneridagh diff --git a/src/components/theme/View/DefaultView.jsx b/src/components/theme/View/DefaultView.jsx index 26f7296c05..cf34ab3cf5 100644 --- a/src/components/theme/View/DefaultView.jsx +++ b/src/components/theme/View/DefaultView.jsx @@ -7,7 +7,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { injectIntl } from 'react-intl'; -import { Container, Segment, Grid, Label } from 'semantic-ui-react'; +import { + Container as SemanticContainer, + Segment, + Grid, + Label, +} from 'semantic-ui-react'; import config from '@plone/volto/registry'; import { getSchema } from '@plone/volto/actions'; import { getWidget } from '@plone/volto/helpers/Widget/utils'; @@ -57,12 +62,15 @@ const DefaultView = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const Container = + config.getComponent({ name: 'Container' }).component || SemanticContainer; + // If the content is not yet loaded, then do not show anything return contentLoaded ? ( hasBlocksData(content) ? ( -
+ -
+ ) : ( {fieldsets?.map((fs) => { From 176d45b7a2ef16539e855891a4382ef10fb07550 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 4 Dec 2022 23:36:50 -0800 Subject: [PATCH 103/326] Remove duplicate H1-level page title, and inherit from Volto's CHANGELOG.md. (#4049) --- CHANGELOG.md | 2 +- docs/source/release-notes/index.md | 10 +++------- news/4048.documentation | 1 + 3 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 news/4048.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 363031e6b5..7d8ba66102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Change Log +# Volto Release Notes +## 16.3.0 (2022-12-05) + +### Feature + +- Add towncrier support. Create RELEASING.md and move and update Releasing section from README.md into it. @sneridagh @stevepiercy [#3985](https://github.com/plone/volto/issues/3985) +- Translation of roles in user and group control panel. Fix https://github.com/plone/volto/issues/4002 @wesleybl [#4002](https://github.com/plone/volto/issues/4002) +- Use the component registry for `Container` component in DefaultView @sneridagh [#4032](https://github.com/plone/volto/issues/4032) + +### Bugfix + +- Fix GitHub release notes in new Towncrier release config @sneridagh [#3989](https://github.com/plone/volto/issues/3989) +- Clear error message when canceling user add. Fix https://github.com/plone/volto/issues/4006 @wesleybl [#4006](https://github.com/plone/volto/issues/4006) +- Fix subscript and supscript active at same time. @iFlameing [#4011](https://github.com/plone/volto/issues/4011) +- Complete eu translation + [erral] [#4015](https://github.com/plone/volto/issues/4015) +- Complete es translation + [erral] [#4016](https://github.com/plone/volto/issues/4016) +- Add `cypress.config.js` to generator templates @sneridagh [#4021](https://github.com/plone/volto/issues/4021) +- Bump Volto core packages with the current Volto version on Volto release @sneridagh [#4025](https://github.com/plone/volto/issues/4025) + +### Internal + +- Remove unused dangling root appExtras from configuration registry @sneridagh [#4024](https://github.com/plone/volto/issues/4024) + +### Documentation + +- Rewrite "Upgraded core to use Cypress 11" section. @stevepiercy [#3979](https://github.com/plone/volto/issues/3979) +- Include `CHANGELOG.md` at the correct path, depending on context of entire Plone 6 documentation or only Volto documentation. @stevepiercy [#3992](https://github.com/plone/volto/issues/3992) +- Close the open Glossary list. @stevepiercy [#3995](https://github.com/plone/volto/issues/3995) +- Added docs for proper usage of draftjs for richtext widgets. @pnicolli [#4001](https://github.com/plone/volto/issues/4001) +- Document how to change the base font and the font for headings. Describe how to host the font. @ksuess [#4013](https://github.com/plone/volto/issues/4013) + + ## 16.2.0 (2022-11-25) ### Feature diff --git a/news/3979.documentation b/news/3979.documentation deleted file mode 100644 index f5965e8fe8..0000000000 --- a/news/3979.documentation +++ /dev/null @@ -1 +0,0 @@ -Rewrite "Upgraded core to use Cypress 11" section. @stevepiercy diff --git a/news/3985.feature b/news/3985.feature deleted file mode 100644 index 8807777031..0000000000 --- a/news/3985.feature +++ /dev/null @@ -1 +0,0 @@ -Add towncrier support. Create RELEASING.md and move and update Releasing section from README.md into it. @sneridagh @stevepiercy diff --git a/news/3989.bugfix b/news/3989.bugfix deleted file mode 100644 index 7e470e22fd..0000000000 --- a/news/3989.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix GitHub release notes in new Towncrier release config @sneridagh diff --git a/news/3992.documentation b/news/3992.documentation deleted file mode 100644 index 804188f62d..0000000000 --- a/news/3992.documentation +++ /dev/null @@ -1 +0,0 @@ -Include `CHANGELOG.md` at the correct path, depending on context of entire Plone 6 documentation or only Volto documentation. @stevepiercy diff --git a/news/3995.documentation b/news/3995.documentation deleted file mode 100644 index 51ddde3225..0000000000 --- a/news/3995.documentation +++ /dev/null @@ -1 +0,0 @@ -Close the open Glossary list. @stevepiercy diff --git a/news/4001.documentation b/news/4001.documentation deleted file mode 100644 index 3eca5c7f89..0000000000 --- a/news/4001.documentation +++ /dev/null @@ -1 +0,0 @@ -Added docs for proper usage of draftjs for richtext widgets. @pnicolli diff --git a/news/4002.feature b/news/4002.feature deleted file mode 100644 index b97d9c2d5d..0000000000 --- a/news/4002.feature +++ /dev/null @@ -1 +0,0 @@ -Translation of roles in user and group control panel. Fix https://github.com/plone/volto/issues/4002 @wesleybl diff --git a/news/4006.bugfix b/news/4006.bugfix deleted file mode 100644 index c1ccc25204..0000000000 --- a/news/4006.bugfix +++ /dev/null @@ -1 +0,0 @@ -Clear error message when canceling user add. Fix https://github.com/plone/volto/issues/4006 @wesleybl diff --git a/news/4011.bugfix b/news/4011.bugfix deleted file mode 100644 index dd0a6594eb..0000000000 --- a/news/4011.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix subscript and supscript active at same time. @iFlameing \ No newline at end of file diff --git a/news/4013.documentation b/news/4013.documentation deleted file mode 100644 index 235ea1489b..0000000000 --- a/news/4013.documentation +++ /dev/null @@ -1 +0,0 @@ -Document how to change the base font and the font for headings. Describe how to host the font. @ksuess \ No newline at end of file diff --git a/news/4015.bugfix b/news/4015.bugfix deleted file mode 100644 index cce3ae5d34..0000000000 --- a/news/4015.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Complete eu translation -[erral] diff --git a/news/4016.bugfix b/news/4016.bugfix deleted file mode 100644 index 60e8634e5f..0000000000 --- a/news/4016.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Complete es translation -[erral] diff --git a/news/4021.bugfix b/news/4021.bugfix deleted file mode 100644 index 5322a06674..0000000000 --- a/news/4021.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add `cypress.config.js` to generator templates @sneridagh diff --git a/news/4024.internal b/news/4024.internal deleted file mode 100644 index 85ca8d88cb..0000000000 --- a/news/4024.internal +++ /dev/null @@ -1 +0,0 @@ -Remove unused dangling root appExtras from configuration registry @sneridagh diff --git a/news/4025.bugfix b/news/4025.bugfix deleted file mode 100644 index 113eb5e3b2..0000000000 --- a/news/4025.bugfix +++ /dev/null @@ -1 +0,0 @@ -Bump Volto core packages with the current Volto version on Volto release @sneridagh diff --git a/news/4032.feature b/news/4032.feature deleted file mode 100644 index 618c744761..0000000000 --- a/news/4032.feature +++ /dev/null @@ -1 +0,0 @@ -Use the component registry for `Container` component in DefaultView @sneridagh diff --git a/package.json b/package.json index ac98fb312b..4ed3ead03a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "16.0.0", + "version": "16.3.0", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json index 0152a612bd..06313b5432 100644 --- a/packages/volto-slate/package.json +++ b/packages/volto-slate/package.json @@ -1,6 +1,6 @@ { "name": "@plone/volto-slate", - "version": "16.3.1", + "version": "16.3.0", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From bab21641279645641227d7593b32741ec21d59ac Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 5 Dec 2022 09:24:43 +0100 Subject: [PATCH 105/326] Amend changelog for generator and scripts for towncrier --- packages/generator-volto/CHANGELOG.md | 10 +++++++- packages/generator-volto/towncrier.toml | 33 +++++++++++++++++++++++++ packages/scripts/CHANGELOG.md | 10 +++++++- packages/scripts/towncrier.toml | 33 +++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 packages/generator-volto/towncrier.toml create mode 100644 packages/scripts/towncrier.toml diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 6c3a5e4006..395b0f24f1 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -1,4 +1,12 @@ -# Change Log +# Volto Generator Release Notes + + + + ## 6.1.1 (2022-11-24) diff --git a/packages/generator-volto/towncrier.toml b/packages/generator-volto/towncrier.toml new file mode 100644 index 0000000000..3ef721f378 --- /dev/null +++ b/packages/generator-volto/towncrier.toml @@ -0,0 +1,33 @@ +[tool.towncrier] +filename = "CHANGELOG.md" +directory = "news/" +title_format = "## {version} ({project_date})" +underlines = ["", "", ""] +template = "../scripts/templates/towncrier_template.jinja" +start_string = "\n" +issue_format = "[#{issue}](https://github.com/plone/volto/issues/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "Feature" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bugfix" +showcontent = true + +[[tool.towncrier.type]] +directory = "internal" +name = "Internal" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 2112481ef8..d049e05373 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -1,4 +1,12 @@ -# Change Log +# Volto Scripts Release Notes + + + + ## 2.2.1 (2022-11-24) diff --git a/packages/scripts/towncrier.toml b/packages/scripts/towncrier.toml new file mode 100644 index 0000000000..7809c3ea6c --- /dev/null +++ b/packages/scripts/towncrier.toml @@ -0,0 +1,33 @@ +[tool.towncrier] +filename = "CHANGELOG.md" +directory = "news/" +title_format = "## {version} ({project_date})" +underlines = ["", "", ""] +template = "./templates/towncrier_template.jinja" +start_string = "\n" +issue_format = "[#{issue}](https://github.com/plone/volto/issues/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "Feature" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bugfix" +showcontent = true + +[[tool.towncrier.type]] +directory = "internal" +name = "Internal" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true From 99b51b9b3011889612fe97f01523cd4332b0c910 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 5 Dec 2022 09:27:23 +0100 Subject: [PATCH 106/326] Release generate-volto 6.1.2 --- packages/generator-volto/CHANGELOG.md | 7 +++++++ packages/generator-volto/news/4021.bugfix | 1 - packages/generator-volto/package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 packages/generator-volto/news/4021.bugfix diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index 395b0f24f1..f7c8eff59d 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -8,6 +8,13 @@ +## 6.1.2 (2022-12-05) + +### Bugfix + +- Add `cypress.config.js` to generator templates @sneridagh [#4021](https://github.com/plone/volto/issues/4021) + + ## 6.1.1 (2022-11-24) ### Bugfix diff --git a/packages/generator-volto/news/4021.bugfix b/packages/generator-volto/news/4021.bugfix deleted file mode 100644 index 5322a06674..0000000000 --- a/packages/generator-volto/news/4021.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add `cypress.config.js` to generator templates @sneridagh diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index b0f2ec01cd..d3a1d91cf8 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "6.1.1", + "version": "6.1.2", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" From 486d850d37def6c3316937036aec4a1b31ac63c2 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Thu, 8 Dec 2022 20:46:37 +0200 Subject: [PATCH 107/326] Pnpm compatibility (#4023) --- news/4023.internal | 1 + package.json | 18 +++++-- theme/theme.config | 6 +-- yarn.lock | 131 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 118 insertions(+), 38 deletions(-) create mode 100644 news/4023.internal diff --git a/news/4023.internal b/news/4023.internal new file mode 100644 index 0000000000..95ac9d3f71 --- /dev/null +++ b/news/4023.internal @@ -0,0 +1 @@ +Make Volto compatible with pnpm as package manager diff --git a/package.json b/package.json index 4ed3ead03a..d3a07628b0 100644 --- a/package.json +++ b/package.json @@ -229,6 +229,9 @@ "@babel/plugin-proposal-json-strings": "7.18.6", "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6", "@babel/plugin-proposal-throw-expressions": "7.18.6", + "@babel/plugin-syntax-export-namespace-from": "7.8.3", + "@babel/runtime": "7.20.6", + "@babel/types": "7.20.5", "@loadable/babel-plugin": "5.13.2", "@loadable/component": "5.14.1", "@loadable/server": "5.14.0", @@ -257,6 +260,7 @@ "cypress-axe": "1.0.0", "cypress-file-upload": "5.0.8", "debug": "4.3.2", + "decorate-component-with-props": "1.2.1", "deep-freeze": "0.0.1", "dependency-graph": "0.10.0", "detect-browser": "5.1.0", @@ -282,14 +286,18 @@ "eslint-plugin-react": "7.20.0", "eslint-plugin-react-hooks": "4.0.2", "express": "4.17.1", + "filesize": "6", "glob": "7.1.6", "hamburgers": "1.1.3", "history": "4.10.1", + "hoist-non-react-statics": "3.3.2", "html-webpack-plugin": "4.5.2", "http-proxy-middleware": "2.0.1", "husky": "8.0.1", "identity-obj-proxy": "3.0.0", "image-extensions": "1.1.0", + "immutable": "3", + "is-hotkey": "0.2.0", "is-url": "1.2.4", "jest-file": "1.0.0", "jsonwebtoken": "8.3.0", @@ -304,6 +312,7 @@ "lodash-webpack-plugin": "0.11.5", "mini-css-extract-plugin": "0.9.0", "moment": "2.24.0", + "object-assign": "4.1.1", "pofile": "1.0.10", "postcss": "8.4.13", "postcss-flexbugs-fixes": "5.0.2", @@ -311,6 +320,7 @@ "postcss-load-config": "3.1.4", "postcss-loader": "4.3.0", "postcss-overrides": "3.1.4", + "prepend-http": "2", "prettier": "2.0.5", "pretty-bytes": "5.3.0", "prismjs": "1.27.0", @@ -351,6 +361,7 @@ "react-sortable-hoc": "2.0.0", "react-test-renderer": "17.0.2", "react-toastify": "5.4.1", + "react-transition-group": "4.4.5", "react-virtualized": "9.22.3", "redraft": "0.10.2", "redux": "4.1.0", @@ -369,6 +380,7 @@ "slate-hyperscript": "0.81.3", "slate-react": "0.83.2", "start-server-and-test": "1.14.0", + "style-loader": "2", "stylelint": "14.0.1", "stylelint-config-idiomatic-order": "8.1.0", "stylelint-config-prettier": "8.0.1", @@ -379,8 +391,10 @@ "svgo-loader": "2.2.1", "tlds": "1.203.1", "undoo": "0.5.0", + "universal-cookie": "4.0.4", "universal-cookie-express": "4.0.3", "use-deep-compare-effect": "1.8.1", + "uuid": "^8.3.2", "webpack": "4.46.0", "webpack-dev-server": "3.11.0", "webpack-node-externals": "3.0.0", @@ -409,10 +423,6 @@ }, "resolutions": { "http-proxy": "^1.18.1", - "draft-js/immutable": "3.8.2", - "draft-js-block-breakout-plugin/immutable": "3.8.2", - "draft-js-inline-toolbar-plugin/immutable": "3.8.2", - "draft-js-plugins-editor/immutable": "3.8.2", "ua-parser-js": "0.7.28" }, "volta": { diff --git a/theme/theme.config b/theme/theme.config index 9cb3b0e9e4..58c1dd0c77 100644 --- a/theme/theme.config +++ b/theme/theme.config @@ -82,10 +82,10 @@ *******************************/ /* Path to theme packages */ -@themesFolder : '../../theme/themes'; +@themesFolder : '~volto-themes'; /* Path to site override folder */ -@siteFolder : "../../theme"; +@siteFolder : "~@root/../themes/site"; /******************************* @@ -93,6 +93,6 @@ *******************************/ @import (multiple) "~semantic-ui-less/theme.less"; -@fontPath : "../../@{theme}/assets/fonts"; +@fontPath : "~volto-themes/@{theme}/assets/fonts"; /* End Config */ diff --git a/yarn.lock b/yarn.lock index b6c17abd6e..cb731d2caa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -347,6 +347,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.19.4": + version: 7.19.4 + resolution: "@babel/helper-string-parser@npm:7.19.4" + checksum: b2f8a3920b30dfac81ec282ac4ad9598ea170648f8254b10f475abe6d944808fb006aab325d3eb5a8ad3bea8dfa888cfa6ef471050dae5748497c110ec060943 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -758,7 +765,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-export-namespace-from@npm:^7.8.3": +"@babel/plugin-syntax-export-namespace-from@npm:7.8.3, @babel/plugin-syntax-export-namespace-from@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-export-namespace-from@npm:7.8.3" dependencies: @@ -1583,6 +1590,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:7.20.6": + version: 7.20.6 + resolution: "@babel/runtime@npm:7.20.6" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 42a8504db21031b1859fbc0f52d698a3d2f5ada9519eb76c6f96a7e657d8d555732a18fe71ef428a67cc9fc81ca0d3562fb7afdc70549c5fec343190cbaa9b03 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.2, @babel/runtime@npm:^7.4.5, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.7.7, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.19.0 resolution: "@babel/runtime@npm:7.19.0" @@ -1621,6 +1637,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:7.20.5": + version: 7.20.5 + resolution: "@babel/types@npm:7.20.5" + dependencies: + "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: 773f0a1ad9f6ca5c5beaf751d1d8d81b9130de87689d1321fc911d73c3b1167326d66f0ae086a27fb5bfc8b4ee3ffebf1339be50d3b4d8015719692468c31f2d + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.18.10, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.19.3, @babel/types@npm:^7.2.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.7.0, @babel/types@npm:^7.8.3": version: 7.19.3 resolution: "@babel/types@npm:7.19.3" @@ -2742,6 +2769,9 @@ __metadata: "@babel/plugin-proposal-json-strings": 7.18.6 "@babel/plugin-proposal-nullish-coalescing-operator": 7.18.6 "@babel/plugin-proposal-throw-expressions": 7.18.6 + "@babel/plugin-syntax-export-namespace-from": 7.8.3 + "@babel/runtime": 7.20.6 + "@babel/types": 7.20.5 "@loadable/babel-plugin": 5.13.2 "@loadable/component": 5.14.1 "@loadable/server": 5.14.0 @@ -2776,6 +2806,7 @@ __metadata: cypress-axe: 1.0.0 cypress-file-upload: 5.0.8 debug: 4.3.2 + decorate-component-with-props: 1.2.1 deep-freeze: 0.0.1 dependency-graph: 0.10.0 detect-browser: 5.1.0 @@ -2801,15 +2832,19 @@ __metadata: eslint-plugin-react: 7.20.0 eslint-plugin-react-hooks: 4.0.2 express: 4.17.1 + filesize: 6 full-icu: 1.4.0 glob: 7.1.6 hamburgers: 1.1.3 history: 4.10.1 + hoist-non-react-statics: 3.3.2 html-webpack-plugin: 4.5.2 http-proxy-middleware: 2.0.1 husky: 8.0.1 identity-obj-proxy: 3.0.0 image-extensions: 1.1.0 + immutable: 3 + is-hotkey: 0.2.0 is-url: 1.2.4 jest: 26.6.3 jest-environment-jsdom: ^26 @@ -2826,6 +2861,7 @@ __metadata: lodash-webpack-plugin: 0.11.5 mini-css-extract-plugin: 0.9.0 moment: 2.24.0 + object-assign: 4.1.1 pofile: 1.0.10 postcss: 8.4.13 postcss-flexbugs-fixes: 5.0.2 @@ -2833,6 +2869,7 @@ __metadata: postcss-load-config: 3.1.4 postcss-loader: 4.3.0 postcss-overrides: 3.1.4 + prepend-http: 2 prettier: 2.0.5 pretty-bytes: 5.3.0 prismjs: 1.27.0 @@ -2874,6 +2911,7 @@ __metadata: react-sortable-hoc: 2.0.0 react-test-renderer: 17.0.2 react-toastify: 5.4.1 + react-transition-group: 4.4.5 react-virtualized: 9.22.3 redraft: 0.10.2 redux: 4.1.0 @@ -2893,6 +2931,7 @@ __metadata: slate-hyperscript: 0.81.3 slate-react: 0.83.2 start-server-and-test: 1.14.0 + style-loader: 2 stylelint: 14.0.1 stylelint-config-idiomatic-order: 8.1.0 stylelint-config-prettier: 8.0.1 @@ -2904,9 +2943,11 @@ __metadata: tlds: 1.203.1 tmp: 0.2.1 undoo: 0.5.0 + universal-cookie: 4.0.4 universal-cookie-express: 4.0.3 use-deep-compare-effect: 1.8.1 use-trace-update: 1.3.2 + uuid: ^8.3.2 webpack: 4.46.0 webpack-dev-server: 3.11.0 webpack-node-externals: 3.0.0 @@ -9386,7 +9427,7 @@ __metadata: languageName: node linkType: hard -"decorate-component-with-props@npm:^1.0.2": +"decorate-component-with-props@npm:1.2.1, decorate-component-with-props@npm:^1.0.2": version: 1.2.1 resolution: "decorate-component-with-props@npm:1.2.1" peerDependencies: @@ -11592,6 +11633,13 @@ __metadata: languageName: node linkType: hard +"filesize@npm:6, filesize@npm:^6.1.0": + version: 6.4.0 + resolution: "filesize@npm:6.4.0" + checksum: 83619b0a656225e84ba9a73271b80091629c0e88c2936c1ebd36fff96fb0e2fbae0273c2caccd522c02bc1a32ad9eba869c28c6b2c36e06187d25fd298c3dfe8 + languageName: node + linkType: hard + "filesize@npm:6.1.0": version: 6.1.0 resolution: "filesize@npm:6.1.0" @@ -11606,13 +11654,6 @@ __metadata: languageName: node linkType: hard -"filesize@npm:^6.1.0": - version: 6.4.0 - resolution: "filesize@npm:6.4.0" - checksum: 83619b0a656225e84ba9a73271b80091629c0e88c2936c1ebd36fff96fb0e2fbae0273c2caccd522c02bc1a32ad9eba869c28c6b2c36e06187d25fd298c3dfe8 - languageName: node - linkType: hard - "fill-range@npm:^4.0.0": version: 4.0.0 resolution: "fill-range@npm:4.0.0" @@ -13055,14 +13096,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^2.3.1, hoist-non-react-statics@npm:^2.5.0": - version: 2.5.5 - resolution: "hoist-non-react-statics@npm:2.5.5" - checksum: ee2d05e5c7e1398ad84a15b0327f66bd78f38a8e0015e852f954b36434e32eb7e942d5357505020a3a1147f247b165bf1e69d72393e3accab67cafdafeb86230 - languageName: node - linkType: hard - -"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.1.0, hoist-non-react-statics@npm:^3.2.1, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": +"hoist-non-react-statics@npm:3.3.2, hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.1.0, hoist-non-react-statics@npm:^3.2.1, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -13071,6 +13105,13 @@ __metadata: languageName: node linkType: hard +"hoist-non-react-statics@npm:^2.3.1, hoist-non-react-statics@npm:^2.5.0": + version: 2.5.5 + resolution: "hoist-non-react-statics@npm:2.5.5" + checksum: ee2d05e5c7e1398ad84a15b0327f66bd78f38a8e0015e852f954b36434e32eb7e942d5357505020a3a1147f247b165bf1e69d72393e3accab67cafdafeb86230 + languageName: node + linkType: hard + "hoopy@npm:^0.1.4": version: 0.1.4 resolution: "hoopy@npm:0.1.4" @@ -13533,13 +13574,20 @@ __metadata: languageName: node linkType: hard -"immutable@npm:3.8.2": +"immutable@npm:3": version: 3.8.2 resolution: "immutable@npm:3.8.2" checksum: 41909b386950ff84ca3cfca77c74cfc87d225a914e98e6c57996fa81a328da61a7c32216d6d5abad40f54747ffdc5c4b02b102e6ad1a504c1752efde8041f964 languageName: node linkType: hard +"immutable@npm:~3.7.4": + version: 3.7.6 + resolution: "immutable@npm:3.7.6" + checksum: 8cccfb22d3ecf14fe0c474612e96d6bb5d117493e7639fe6642fb81e78c9ac4b698dd8a322c105001a709ad873ffc90e30bad7db5d9a3ef0b54a6e1db0258e8e + languageName: node + linkType: hard + "import-fresh@npm:^2.0.0": version: 2.0.0 resolution: "import-fresh@npm:2.0.0" @@ -14179,6 +14227,13 @@ __metadata: languageName: node linkType: hard +"is-hotkey@npm:0.2.0": + version: 0.2.0 + resolution: "is-hotkey@npm:0.2.0" + checksum: 97d295cfd8c3eb2c9b218daee5bff0ddaf47210930e3eb1eff36d2e6ad74854028203c640f35ea2d183d0cba94ac4c4bcd291925bc3a343d8a4c7d2c5ab3e2a6 + languageName: node + linkType: hard + "is-hotkey@npm:^0.1.6": version: 0.1.8 resolution: "is-hotkey@npm:0.1.8" @@ -17498,7 +17553,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:4.x, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": +"object-assign@npm:4.1.1, object-assign@npm:4.x, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -19089,6 +19144,13 @@ __metadata: languageName: node linkType: hard +"prepend-http@npm:2": + version: 2.0.0 + resolution: "prepend-http@npm:2.0.0" + checksum: 7694a9525405447662c1ffd352fcb41b6410c705b739b6f4e3a3e21cf5fdede8377890088e8934436b8b17ba55365a615f153960f30877bf0d0392f9e93503ea + languageName: node + linkType: hard + "prepend-http@npm:^1.0.0": version: 1.0.4 resolution: "prepend-http@npm:1.0.4" @@ -20620,7 +20682,7 @@ __metadata: languageName: node linkType: hard -"react-transition-group@npm:^4, react-transition-group@npm:^4.3.0": +"react-transition-group@npm:4.4.5, react-transition-group@npm:^4, react-transition-group@npm:^4.3.0": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" dependencies: @@ -21025,6 +21087,13 @@ __metadata: languageName: node linkType: hard +"regenerator-runtime@npm:^0.13.11": + version: 0.13.11 + resolution: "regenerator-runtime@npm:0.13.11" + checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.13.4, regenerator-runtime@npm:^0.13.7": version: 0.13.9 resolution: "regenerator-runtime@npm:0.13.9" @@ -23116,27 +23185,27 @@ __metadata: languageName: node linkType: hard -"style-loader@npm:^1.3.0": - version: 1.3.0 - resolution: "style-loader@npm:1.3.0" +"style-loader@npm:2, style-loader@npm:^2.0.0": + version: 2.0.0 + resolution: "style-loader@npm:2.0.0" dependencies: loader-utils: ^2.0.0 - schema-utils: ^2.7.0 + schema-utils: ^3.0.0 peerDependencies: webpack: ^4.0.0 || ^5.0.0 - checksum: 1be9e8705307f5b8eb89e80f3703fa27296dccec349d790eace7aabe212f08c7c8f3ea6b6cb97bc53e82fbebfb9aa0689259671a8315f4655e24a850781e062a + checksum: 21425246a5a8f14d1625a657a3a56f8a323193fa341a71af818a2ed2a429efa2385a328b4381cf2f12c2d0e6380801eb9e0427ed9c3a10ff95c86e383184d632 languageName: node linkType: hard -"style-loader@npm:^2.0.0": - version: 2.0.0 - resolution: "style-loader@npm:2.0.0" +"style-loader@npm:^1.3.0": + version: 1.3.0 + resolution: "style-loader@npm:1.3.0" dependencies: loader-utils: ^2.0.0 - schema-utils: ^3.0.0 + schema-utils: ^2.7.0 peerDependencies: webpack: ^4.0.0 || ^5.0.0 - checksum: 21425246a5a8f14d1625a657a3a56f8a323193fa341a71af818a2ed2a429efa2385a328b4381cf2f12c2d0e6380801eb9e0427ed9c3a10ff95c86e383184d632 + checksum: 1be9e8705307f5b8eb89e80f3703fa27296dccec349d790eace7aabe212f08c7c8f3ea6b6cb97bc53e82fbebfb9aa0689259671a8315f4655e24a850781e062a languageName: node linkType: hard @@ -24370,7 +24439,7 @@ __metadata: languageName: node linkType: hard -"universal-cookie@npm:^4.0.0": +"universal-cookie@npm:4.0.4, universal-cookie@npm:^4.0.0": version: 4.0.4 resolution: "universal-cookie@npm:4.0.4" dependencies: From 4b7e39b24fe730b9594b0ad08d0d9bf373c1d453 Mon Sep 17 00:00:00 2001 From: sven Date: Thu, 8 Dec 2022 19:49:03 +0100 Subject: [PATCH 108/326] chore(docker-compose) update traefik config (#4067) --- docker-compose.yml | 4 ++-- news/4067.feature | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/4067.feature diff --git a/docker-compose.yml b/docker-compose.yml index 7bd820f2bd..c9dfff926e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -47,7 +47,7 @@ services: reverse-proxy: # The official v2 Traefik docker image - image: traefik:v2.8 + image: traefik:v2.9 # Enables the web UI and tells Traefik to listen to docker command: --api.insecure=true --providers.docker ports: @@ -57,7 +57,7 @@ services: - "8888:8080" volumes: # So that Traefik can listen to the Docker events - - /var/run/docker.sock:/var/run/docker.sock + - /var/run/docker.sock:/var/run/docker.sock:ro labels: - traefik.http.middlewares.gzip.compress=true - traefik.http.middlewares.gzip.compress.excludedcontenttypes=image/png, image/jpeg, font/woff2 diff --git a/news/4067.feature b/news/4067.feature new file mode 100644 index 0000000000..a687bf12ea --- /dev/null +++ b/news/4067.feature @@ -0,0 +1 @@ +Update Traefik version and make volume mount (docker-compose) read-only \ No newline at end of file From 3d32318bb60743337c014df2850c914474117a0e Mon Sep 17 00:00:00 2001 From: Mohammad Hussain <99530996+MAX-786@users.noreply.github.com> Date: Fri, 9 Dec 2022 00:20:47 +0530 Subject: [PATCH 109/326] Reset value of input field of SearchWidget. (#4068) --- news/4028.bugfix | 1 + src/components/theme/SearchWidget/SearchWidget.jsx | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 news/4028.bugfix diff --git a/news/4028.bugfix b/news/4028.bugfix new file mode 100644 index 0000000000..3f0b69bd2f --- /dev/null +++ b/news/4028.bugfix @@ -0,0 +1 @@ +Reset value of search field after submit. [@MAX-786] diff --git a/src/components/theme/SearchWidget/SearchWidget.jsx b/src/components/theme/SearchWidget/SearchWidget.jsx index 59bceeccc5..5dc5e40513 100644 --- a/src/components/theme/SearchWidget/SearchWidget.jsx +++ b/src/components/theme/SearchWidget/SearchWidget.jsx @@ -81,6 +81,10 @@ class SearchWidget extends Component { this.props.history.push( `/search?SearchableText=${encodeURIComponent(this.state.text)}${path}`, ); + // reset input value + this.setState({ + text: '', + }); event.preventDefault(); } From a8e809dc1c4fa99b511e15fad3ac74bd02fa3f65 Mon Sep 17 00:00:00 2001 From: Mohammad Hussain <99530996+MAX-786@users.noreply.github.com> Date: Fri, 9 Dec 2022 16:04:19 +0530 Subject: [PATCH 110/326] Add content for user-manual of Volto, Plone 6 frontend (#4069) --- .../user-manual/blocks/add_new_block.gif | Bin 0 -> 453165 bytes .../blocks/block_left_plus_icon.png | Bin 0 -> 5748 bytes .../user-manual/blocks/blocks_type_menu.png | Bin 0 -> 34286 bytes docs/source/index.md | 1 + docs/source/user-manual/blocks.md | 85 ++++++++++++++++++ docs/source/user-manual/index.md | 33 +++++++ news/3827.documentation | 1 + 7 files changed, 120 insertions(+) create mode 100644 docs/source/_static/user-manual/blocks/add_new_block.gif create mode 100644 docs/source/_static/user-manual/blocks/block_left_plus_icon.png create mode 100644 docs/source/_static/user-manual/blocks/blocks_type_menu.png create mode 100644 docs/source/user-manual/blocks.md create mode 100644 docs/source/user-manual/index.md create mode 100644 news/3827.documentation diff --git a/docs/source/_static/user-manual/blocks/add_new_block.gif b/docs/source/_static/user-manual/blocks/add_new_block.gif new file mode 100644 index 0000000000000000000000000000000000000000..29425365e9b01c50c4c322e65dd54520adffd0e6 GIT binary patch literal 453165 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

eil)sqAY@F6L0w3fJAC)8&ub25o1xsTlJ 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

KqhVEE8Hfaf)y4^*;x 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

  • OULtkYp4hJk^@ErH%;VpE&7(f_;T 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'TbHS{a#!cmD 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 z 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 z zYS0YQdAwrLa9TZ6%cImd+Z6)+)t_J2jLNN)y>hLf-sUZms|T2xBdSG>GG*6bQt&%e z)AocW@1iE3_9njz4m`KEA1xpUI8bIP}KI-zs6sB^x( z^WBTi56hj4%3U8VyFUANeNE{4UexuYy=y+a{qWnaSfb#Nfgrfuzhk)>3-ak=}?bk+$1=Y2$AUDD$pr)*j5DAKznuV`wAx zq9rYb-LY@czf&PIGJZy`clQhMJOv4=M&*$YL-e>Q>6VQ&B!x#T*|3wZ+*}rqtW)y8 zn$R3VMLh!0Ts4FXFdE3p1@)Ev8=pLmG!D1bx}1KbQMiDg)7012KLBv55n5 z#REq=1`@{xj{O{9s|+St4W{@FrX>z$6c1)Ob__lZ0W9!Ay-E!SQq`FJiy+j_D85yQ5+{t?59 zqYZu=Y(>V=Ci3eXM8x>68@aN9*52gn0C2)ATg|<#dPY!*F2T5SZ04WvL(W)2 z(RvW|R9w*XZd{POwL;b6(1+I@;F#R~jvEIb^IOH~`wbni2M%UioT( zw10h3diY66zy%95INfB6Z0S0->!aS}#hII(#<2p_lvxT6jSo&aiqlqj0^p6G@?vjS zBAgTn^Tp@KkAYkoJ%$lljbROrh%?X^8q8l+bVLJ1BU=X($rYee~w&$N>) zJzb1)Cw;#$^Pg;iU4Y)rKoHvsMxTKmZM)KWbRU;M&xxZAled~cDWy@S^SUCBjt)cZ zMg_pZsN=j+Xxcw>deoJTWRioPniN2)@NQlzd{KXN{vuy)<%>WSQmwZy@pGoy5W4qv zS6+bb`fDoEROE5O!%DmfeGO?O*VwVlZ!M*u4?N9}>o}LTjq}hasQ)sH@ z$1I{EeG<(g=!X1<2knfC&nL<2@rE1y3x_SZ5=oH8zHBd!Ui_5Vf!8EjdFX_z2;#rhd|gK-mUFnk6Yy_tpx( zw4Zv)AgucuJaJ@rUYx{D`n=8k@8{4lZ620xs-XWjZCwIR31g(iG}%M!tUVzO=VrT9 zG83nI@hDFLTWpH1cX~bUf+trt)y@`6XKjtMT4ci!d&&DVK;E<5};SK!r(+QXh7C$iJL3h#=YgmbfgrL8fr5}C#wC_gCVEGLSK z?5*VNQ74Fiea573o<-VrEuGj{NTz4y6gG;%%3E`L>(E2E=jx%uI29gyiyCix5GAL>jF2MFiXRjw-gcJqM>A1#w4g+FTvA?e z2I^TiO@EtnhEw?&e&YkP;sR(*oZv+2h#jR7umYSKWP&LfQhmG>k$;?c;z!`>e7zFS z?O{Y8c9_9YxqE1*=>58w(Zk}#S_(5 zGtLi=JpWYk{qFxA#Q54#2@x@_y7%9R@h>$_?zNuzL!(vE(eSBZS4xawdi;hjwTL2z zUmm&b%8^Iv4}oAptUh9y!oCvdzK0%Ambu|+x10vcu4>d}M-l}*dg$I;gHJcR+!IIVY~&A^>UcT0XZ3R!j^pP7dx`ZqLs7qnWmQfm;H|LcCWCGb1eb>HbhwCB*#v+)$JaK z=*b%!)sVl`MjqT>n&9Qj+fEkUK|Cf3le(*fZO{GnBWKXB7L^{=b_`yNPp_??tv!6x zw8L>1YS}DtvYMu;z-gZUub8$zF?)4`tWIxfmCiJFpk!4*G=I zfFSyegj7DT3oO}niw4ge8WN0 zuRn2C)802tW*q2ykKl~k)!QxuN>V|GIKw<8X)u>gnZ6z;*>_ZGA!?d9nSK~+84fLV z*FIus!@?5HqE$E*pOIE^A4^Sv^)d=xN5XljYCOmdAe@TIC~ys}{(44r<;JB2F#7d( zMOz$qVfl0XU_G52IVeahF3CZ?`=#9TT^_*jI9rhvj4BrI!;QG(u|bcECgem&E(Kyp zumTTUk|Jj?58Y+prTOBdAl-=mO=09nLsnvntxi!7o*e@`*%6{^i0&q1FWV$Grf))d zK-fVnZ-#^g;MhsL9#gy#5!+=;UUdcQ87TW`+ZX^NJ4kA2M!Z&l{$;m=d5nFq4DBm>f=q~5-^6V*5{FkJ2qg2CLkGnoviwFMD4K=!mctGQU2>S zk!)rvb_AzSMd`8fMFglG4?4uq#TWvkhyN&1QMBV0Rg~<%NyURwSSBbzBTG_5oOk}C zpf$-JM2LgC$ir==kuI}wp+aPko1zqja@&ENHbcaRfpzf_UbjhdQe5i9xeNGwOb{L& z1Z*Qq4Ffr4WaJ)=wCkW*{0^D@`22%8W24OW)$&JB)fb_hGBkEFBZxv*RVru zxT<=Lq7+;6$j>P$Tg%@Bl|^&*VsIpz+rXeUNhuQX+>@jwAeE_bSw2V~J#o2M?}`Nd3c}I2J9MS%$CY8Fs}C)%j{01EeB|oW!mH2Qu8uvw z`s&A3;SbqH>1$sMuT34fHd}aYzU|t(=hs5EtIJMan*i(lbn3dh&wedbqG=>pNJoD} z<^AoV|0Eh2B83BY7*!U*2yHWpqo(fYQJobOjn9`@4jYXE!krQ2BUcc*>Vb2Y6##P9 zhVw)tz+2$VsswdG@rC7ikedGuW zr(6W--9cy{>syo>I&vBhb(7IFVDW~!vL{x4{q?mZpSsX>Ig5s$`sqGI586Yu;i%?@ zG6==VKri7AafxO2h;JOpXc6noLTsE7cmCSLLEA(gKUXePvnYm&YJm(j3=PZB^ST z-CN<-GmBh4^rIZ#D;s6rCPj`%vV@tEmAD@d3C~j29(jnE@?2W^kd~dI#znDvo}_5@ zj(TZQ2NQMvGJ>IC>5G8u%Dn7bDSNZt>@|uq`Hb^?vPa>0$d7@sqHe&Joh;s|*h&*u zS}!-Fd`6ReibQwz+DX{y=wK)euS2@dZjjF)${nZXMj0T{z$P^M#?E{!_cGC!)regp zY~U6hi~{%bJNH<+NAmDGRLJZMC@g@M+fj!W37X^$d$c#S4qJ4!gWTlPdwu2CJVRHq z;65sHo`Q~eBmF@aS&P647K~aiWLCP9P}ZI2K~NZW8H+2mVU`dkHfCue2m?4;2-S%| zTy9YvKj|=MCq_gz@(cGC0G1EMmY;c z1hPM(XgXXRT94Eww#M3al`h_R@}%9LjOubl+??eNC=%ExyfKq7sL(nz2ip7XNa{zr zSqDmN^3QT~Kh=PwJz&yaR5Xxy-F3eP6Gu|C#v`cJzE?D#%jS3@%sglf3#Y9F80h3zA ztKuC-hpfrQJZaLQNz%xat!u@)MNq8SR1uscZFNh^+9#kZiX(U9ylse-tfBg~?g4fyM?+Vpl zP#e+5u(5e$?R;hVI0mW#&5O6Vqev}Nr9O1Yc@_}#;$ZzVr_ax{EE**}%xxIv%xCR3 z8n!l4V8D8C&GQ%iQ7UckNz(-7Umh_aQde(uKg59K+o(mgQ5s$8ro6U^#EAe)^2Li2 zV-H@EMt&*%byWHB$<5y<0O$BkV(Q}`o`-|WD&H@R|9m1k9!#o6T4$na%1HnO$d3{hsBj&E;Cp z0J2qEPGFR9+SNv+Oezfu$$V|+{FPVp*M86IQ~}CPa}5D= zEi#Y!0WX#IXP;Qixc z@1K^uf8P0i?A7~MzuybhK1^DFmxIt&3tx{d zd@ot}(Yf&R)xz)J3&5&HXwxDbxQIEvh$~$rbS;XFFG_4-j4bijcE8b5TQa6E>05*J zwbBZulS&KHs^c^2b+fADLnfV^)~ixGJ%DYE>e7flmIX+xT(*2lx=?8|aXkRhE0x;0 z1SosyCzk<6>!_t71H7$VN)0f%vpz^dc3>Pmil?-0Ch-% z_866evfJV=fJI^dNl2alg}w^vQIL`_|2LYeT(FY%73P_RN`mJX@KP)r^cUz z)L|nvFQf6&U)2nqp>8U<_R&AnSuP&ga@Ryoja`>AvWf{If7SZmX?{Q8PKj*0;QC~@ zSaJ?LN!r=+pVL`oI47t}EGfdUM~ea%Q@rNwi>{@cZ?+9v zd0zQ5os|%?^Qk9iMJi&CP&N5x$K}8Woi}&wr3ic1WfYK2!$16)&N{wYlk-Wo!roxd z=Wrnhr=5{UI*m(FiWJY~-HQ_;0u!kU!7+o;__=cv>EtxsayG0gl*RkXOm)juoE$6# z*7nCuz_2^J@PueOOEbav!|dzRG>qO9*G_KtRF0GOrKwyuQxSc|aL{{`=e2$Jo3q|w zBKpcNcJ56+Gfi*0AoSes>GKg)m!=E%HqT8LvHJC9ienz{p1Ba`<0vRe{5q!t#$)tn zgOAG%&G0g`YiG+(n9k2$Dsr~I{r*&2`~ol_7Mz_l~4DrzWtfb`fsJL_Kk`d_7C5#bBNY)*V(Jo_DyE} zp|9*_pbgizy*Ij1r;qdM#!=irKOOnIW#FoOY&!>qjtb7k%W5%>TsGOqmZO()SvK~) zH&4xzT=r4929y|^9#S$_kReYzL}r|ByI6Zm{faM(yXu@--JUEIc!n#{vWNNWO4dGZ zci1slgFzHm=Ywzm+7=WChfEM`UvE_3zwEW z4oHy2COOJ$Nc6SdBOY!UGLkfrhy+QZjdBq}V!efbx_-m>x_0j z&2^ZgY!(=%-N)O>;xn=M)j`O{ypL`$5(>wDJay!5H2EzuNI^rV4aE+UBgM2u&RIl! z^~ZDmg>15NFdoGX&BZ7rn|#uM_vDgcibAq(E3aSa{X;2v-3+O+OI0XJE+tzAT7Q0n z$c;@SD>B32ug)u+=-evs2xZXX*)u(i+ZlLc;WV8zWR=uEC$d~^liiElKI_$3E}dB+ zMpg1y3)0n9zw0`&d3mn605l|hi3Z$8$SU;8b?;}-0W%ioytF{Gdrm@@k1S8cv+6&c z8vm7$T70?0I9cme?eeJB=gYr3H4F~Gz{T?$1YqbrZy(3lE$PLFt_G({L;j}2O$wi{ z9X@$EY5qq&N`-b7pXuyJ<+2;*&$=IfbD0hsfIX&ed&RTNpV&Pn; z94oSRrr1>`DiYarg<+P?%wl&q<*wm=1<*7y04yeRrkJSSiFbgmvdUa;pPicbOvIKv zV6>dCUcOBLe%0f?3(JSoQsqOn?&x%>(ZJ6tPR0?@y_X}lLkU^<%`FDp2a!R5^df)> zj(%RsU^b~fOxi;NUJ%TMNn5;A*6!btJ9eb9nPLv_BjLa@JB;BPZ4fwRz?sq}Yu`PK z)3O@gt{+#49OLi(AazFSt;1C33vnQL3Q3WBp}kfLP>D|tk$M?M4zxrhC6giXx2(>b*|tX9#R z8$kxJDgKUl0VyNpbq`{kR~AdbB^kjsXesiTdL0?e?=k|cVtmP5pg4=+wa ztqoX8Pa{rA`>vSRh}8^dVd&%s=ZpH{nNe67q8+-MAJ0!c98P}9h7);7QW#Hyzf&3M zf$lBB6zkn7ry$YO%`TQzCbnTpU4GB29t2_zXK@+0&;^pmnhV*bYi3N1P)!%X@W#r8QS8^wm5xvgS;iT71Ml&ZTT*Utj2Iq#eiu2 zyML2cuIf1xNL{5wijpRCoSa9GmGmx#CnLa(CTik0&j@68lR=6DJ;`tO<#aAD?Uf}x z8D4xbJ~r96IC*j&GMH*Mh(OaLcscFDa>;ZqdjWpvDAgK8jy7J{r}HqA`R*b4hHlD= zoP_Y;{_z_~&iU)g^9%z0IrAkPgf7xJgY(m*bRk$^2Vn#b zy%#~R39w@kU(CR1Mfv*hkK#!O_=uR4860Q``P0N{6*y7z)1FOOz79rkiJoLfL>H;> zAmryO=CbyVR+4h~XkP~H+SUkEJtW$=5F`83XbC?)$=Yt0qG%&7d}n9}yNAo7iy&GL zg3_XAi6c})gW5!8Bbo($SnLp=mBoquf(8UFYwCm;g944@0c5$C7|o7^a_N zh^H567`k`|HxpHV9F7W?&WY<@FUn=`b`H=3Dj`yUqQq|TXTTDUd*PYgIg7GV=Mjn? zvYCaD<5Jof(gV{GDkKy_0@GIbs9XWMfQr+ol)=lEs*tA`^LNkXuNuhj6qr;?w>Gf^7VV3pNMk$7XK> zOB9?iP*h6oC8uZh=U+tQV!d-^)v;~-Tt{Yxc(~&|1|rYL9s!iqsD4loRwWhD-HwZ< ziRap)XbfC2c`w0m2MR1gm0@=?GkO+_K+_1Y8qq(i@unW`J9Gr1Va7tX>*?pd zeH3c49H|hZ=K})ZVKTf+kX-XP#jIB2U{ks>SdHeuHDvL(JCP${M<*()aW`04Uj(&98+jvh{6P$I!TYjAj>fIm z7*mF2Fdw}TA$p!-jE2DuUgCBNb08)Wvm_?rh&B>pcbG#uqD9RKcjX-7g#t|dVc2T! zjINXqiC2^zBfkbCHv5`XCxl-8#k;GA)Ud1CI2M$6P(`T*R!@P_s+#7pqbywE4gk*w zaz+cG5v=kOHpwBMA^?=BSW8`SX(g;?EX;1nbO(;-Ei;0fb3xW}YmD0~G4%Wdh0C~+ zNSa+Wg$m;YXek$n%!Xxlr)H6@vTK|m&XwoU;XOJCf)t3C!J1WJxWkv>5pd1lS2#*2 ze7g;Wj85PZ*$$8a*%8Iv%iVym%n`cqinxczrYGrUc~_l(09PzdQ$zqSuN4hOQy2st zfki3xGKK4@9F&ny#Ls}R&>0;!ga|K@APS=3?F$HlB*@TBJ>=qB*)@|`7v%DNS$NYT zc#KQ{s7FzB@veg4Gqp4v8Lrl`yF-CzIHYB>p!PoCcC2MqS-689a%T9D;)I3KFi>rP zy!1u1S7>OjVMvDyev=V%arMJ^_=fV}SFbnram7$MSUts#(~crrs^8O(WK+e=Yq9X0 zcOb4Ci0Hn8<^)GqfHhO?m>dl_X zI)%?AY;zw1vc<|9Ac)d@#1Rp= zr6VB481dzvy5)=*F)LN-qxW+~fFCGMc27zvskmF;>fVZ?MP7`4rH8|_!K7$CdNM2NHaX~LmOJ0GS zx%@gGGZNhN2*?}OsIfu@v%4XWQ`#hF@%$}N&R$fvyrDq8^h71Oz6R@Ylfr0};pCx6 zgncZDDgdpXGw`EmO`e-(nIGvNH`7p+g57dF6HJE8VY$3c<1pw4$}Y-c zskhAyCE#33`j>){|Bl}(=!^09Ye2H_60iyawzv+=Z7uIaJ`Ip(qUuj};w5ueV$LP&k(sHAY zyF2tH9-+XJFes_N(d`AzwU{7rj`>J7>M>^Gky@^=iH5wf(PN9HYesTl9`|v=N$D44 zII`j)T=vX_S0@()G*&#LFi?xUx)9b?>oW9_3xiLBl}}DxR{-y!Cjx)paz;IJ4*#;< z=44=6Wi3l9Ef8 z5s^D3(ghmF?vil`5{345_iZ&I;#^H= z_={A+qm*&r%}7GS7Pa?Ml0v|biaE=2JBQ#O+quZ7;UnRa9HHeaxE$4ueofgj5gKJO z>@I0dP!zt%-X=}rKgVAj3%KKS@2cH{liNqccZ{mOgdV*lcfY*w`6YG<_-4GtmZxOH z?Y)ccf$4buUL;T8H|_{5-yHvSVb|&$@JQb?4CgL961=O^?bXql$5=o$09>U8s(+CB z6;pzW0s>1qf~*mjmAvuTwa90$=}4Hd(FXsf@mBI(za*2Xo?mO1Wx~XQba#-#iaO_H z$_CImU4Ii9ylb;6-E-fs&MVd+*7u$!^Z?7F2QZ-bKGS1RC+Va8{LL)6nv1nZjV{9c z?V(NEzhqjp$85jwd1ehA@Gcx}{SYKg58QYYz>R8Dl+grh!|F>0AnAFDiMj1L^OM%2 z6MN@D_Wbz3{N-!2%Z>gYC4lRXWdfq6$c9Qo9mw%q|IkC*p`7iG`$i>(-zhIkjrmJ) z{1Mb~?~)+hfq?C<9~N2A(!i_B58Rgy1nVYvzElcaQdwA9wVchN6pjPS_j@S zNj3e#FCkJ(fs$W?9)1aZ@MZh)uff4zBLlzg z9{=j^Ii@Q4%`qF^oVo8F)099GU@{PYcH~OxLcuz%OFSHw4_plTR`!5$S{}nAqbB11zI1@%0w&=| z7e*56J^+UjkhwNA^qH!ZW zucH9!Q=(j#BBn-(dLxv>D@c)36g(#FvSil;^c8r#b~vTF@ffV=IpvhA#JWw8q#c!xmJj9Av8z(3 zHh7&1z726*AJtaeY>ICyxd}G3BRNu*tWlfo4DDsbb?fcr1M@ZmxUhzh5_;X;Y&>QC<_$9y*Ed@(^sV2r@!Qn(Et^(gV+I|o)yS}wv^QSyu$SM} z=;633)!5Tnr?S!0b=@uFt?p)T8nK&A>)$AQ|+TL{MuKZoiJHty;O?`G(S2p|Xsk>$ByRYR< zv+sc}w3%PjfL4p&!3Xwc{)e9IYVkijo@y2lKU3Kfkg#yeEb!>JH%}^160>H^#^187 z%$i^>6H1m3YYm!ILmmcY>QpHhE9&=u*K*0b(;AX@?2~zDZh-WS&@)>cHtagPBkabm z{J^vg{-;8!ZiE#b=-&{2;qdg0@Y3TLi-@vx?Y4-EryVSISLTPc?XE6OvxvM@UDdV{ zdAY9NV$ao<>9##}T^P%~^#j`Nd#^umu-w=5B&>a3%XpgQ{u?t@?fcso`YjLK{5IWw zplbzV#S$rvJ6OGvj#g2(w`?ufp#Zoul`J+qmPgAX=gH^z)O>fDU^ zYm4K?LyvZZ-#qjrFnwd}v+(Mhu`dn`Y&`t(@XXD_*%-f^i9Y9o~iCS zw!AQ~X{D-i11AOjIbF(I%u^1~zYY1A3jnbmKx(RH4GBeqDc-b6J08U5h;Yn@7d|i+ z*O|-k*jRgW8>w@{DLQsRgl(-ha?Qi3>r|AmE2v)5ZN$>JFG%80tF78s#*Y8Iq3Hd& zp-_QY5dA+>=E*qeKVI?)8@!atzctuXj68u2Ho}Xy&**L~2@MUs!;wL@4HY|Iuew)c z+JxlgPUbj?nTu{H^GJ~(-|mdQ|4^fztt5J5ic`+pQ=6;3um3}t?7?pD|S{K5|-flC~dOW!+8^2Auiru2xf3I`oQyF?XeQT>dK!bt_%J2`|H zhoTF`?a)D84v;KIQ+o7-WRSO;uNtgslX7q|DD;fk~a2vM@eW8*R-PN$4{yotLfeK zV4(cCA#70X%8Hm7GwkJRL`#uM9R|hi$gMQpm3e|`{{nfzS5wf<1Y;_Y=!9SipiM01uSIwl(z z9!VeCxb%#OW*@P7`+?Sd_2}t^1&Pcp)_WlKx&zMl=3X7$fj0K)Y7;l+s@pEVPNkz| z^w|JbSKu-CAOb%ZDE8gHXbqyR9nHy*Yvi@#g8|g*JHs%kpE=L&d#3I_M&~CO}FfSGD#Eu+K&VVwWo|(u7#8Fz zcJ_&O0tN&0WS*b`dZTROTS2xakDID-BjlFdlOA~j02%oa#qquDenpDH85Kg8AU|2N zm2ud$LkKZH_T^EpWDIo&6Yf9d;%!nQd3geGv!1+-n4Lu0nA2R0SyCE&gpg&L!#(GbFGhG~AkxDVvbKLes?*S}rg1d_jLGrVQgAx8$CbR~`ywb{lP= zBZ|VwQz$*OG1?Q$={N{?i=}D6o+|{&2s$cb!hYkg=^Xp-!zij-#wt0FEt0`PxyPX@ ziXKvTW#U$&^#@ax5m5(`W{-CE&e%-t#&V1`(1l1S!A>YAF+~@@LCuUDuFip56+FQq_s|csj6q+ z(Hzbvy;ACvZqcoC*vt7Vji!OdexFu9<%;0l02KUYkl@K1pU_U}yz10V*u3bPq+*V) z((Mg)d5jkQA71ynF}njh8E~>b|pV^Clk2jcDFF_hz1F)hK5`#SPSU6D{<84kf4R;u0=uAj*B-n)qS%rqn$a z$tn{ofWlKKZh`HlxQe-E0Ljn~wjgcxHX6Jeij&0UVm2+7lZW^R2)uZ}Dlcf{^1f`O zp9`79oYho_I7rYf;>ul&(YP?&t?z#EgzJ=sf}C20TAU#+_6X3Ty=1($FlzRc70+-p z8+{HP?DR?HqU>2Np1YMhPa{%7(dRi-rReK^i&NO9%^dkNge}sAc2cfdgX{gM_mZ!W zAz)ZzE&D~LbIr)}UpHwf%H_DtEFZGSU!}b{$cas$D5Tndqa}@?l6bUQNHJAOkN~(S zr$8SL60kD+q#Bh%kD}sR93n2`?^Xy3Lad$aNEwCXcQti^wNkw5}V)C^LyVNoD@NQy7x#2JX z<$n}YO5sMpI~DtpgD05P%u?M=xV51ICl0HPOYEprO03~t;%~%0yR<>OCdla`8pK;m zKxm;ESJCvAaO`ZdxPD=d+o}ozaRa2GRgQ8YUn9pm;7s&$-Y46YYn~^6J-KyQtfr%U z4mwZIG|Ww7&L)v*mrl?$N^@mr^U0e-HrnKwa?1|vKWkTo5s!(E=d1?I2;Kt7r5ICA zv{%{s_q%c;ICa^a4AnRvCj6k1aK)doZ_Zw$`3ZMPN`@XaSTtU_kIzjmC5E2To5jqo zABTV{@+V(T{DoTwNWH#rhiqo#t-rImd|8TAr3u1a+>DPRisYzos6;(PELRJiC&Z~N zVNJQFr?rs;J~mt=z2^e}%VREPuSyxF!4YXKgMU>aR(Adbb&L%;@VXW2wkSm5x~vm2he31VK2y9-FNV?f~6aF{53#A}-`q2*i7m zv0@BBk%pIGf-eWmRl8%wNZU|BvZM)Hg-<~32v|<`l~qS8lA{Pg^4s_j9RR(0QMbYn z*|V2k~>HjP&CKj!{XBJ&m#H zP$Q{u<+U*?Y6Dd7!LRY5&LNI0_){psglUwmi)gSx9TAgA6Zd5XwNBYQZ*F0K8Bjg`ccTN9X6We3(kfyZ66{bxOeSOlDQqxQR{gX-n1T|u!h8ORkdQ<&qqH7SOhM|OMh_W!OtCEhDrZC z4-JY&jzsnPC5gddi3Ns4rylZxi5F`@mZdclVx&I4Mrzf~r7RF3trUZmtmQ(E8%p1Z z{_ZYaWa4X?c*!g-JW^s@#-jxR=Fw@MGh2 zFA2{8544%-aZ_H-oR@22mFxy~iBwNQ!mp{YZq+}wLzz3OdVtO@`0$vl=L4Yh;x>E>VrGj^x@a%XT2B%lOO3j8qO9`%#qG0EwnkA` z;}4s@W!!sK(!|mM(W1 zP3x_;7&Ou8P1@UijSpVK&UkwBG(FyWLFC*!CB)h4@ z$=TdMtqn#6tvHhCa{}?Rt)d3|K||s-G9pjjbuJq*OTVCp;Axz_7qXFhDxykLc# zlfqC@>z&L+`*m%wxfDyZ(l}>R|wnjOX8>?)(I|D#Dj{s(D};;&iN*q zkfE=)#QU-lKN;Zf!WI@{YSs&~br8YIqRlIF`qQFTg)=E(#t3bYqp%fFpu!i1+Cq$7 zLhO?8WZJ^=#$^4DpD((8FLwbd0?0}L`w1|K0$j160<0Aq6Np_AAS&HbR-!lXZn?y6 zh2n0dj&7B)ZndA?WR)Hbs~(D9k9J~@ZgG!(N6+f9p0z)Fs4BfiR=vi4y{3u1=Ec1h z9lcg#z1BZ_X)1j-R(*DUeGZ9zPQ`sL9eukmS7TL*e`gn>i*LHASR!Tux`Ducxy#q@ zR>03&OgE(r<^E8={;s?*ESgH4*H|k%osfbV1t_3XcDw+WQCg{6SLQD(1tBR&{NDXL~*35A13BS}qfK zK6F7j27K8b_i4UzewPUL)T!)fW7xRb2{NP&0`9W1;eM{BnH6Q1kOSwg-}7fcE&MuB zA|rKb=K9=UHM=|gGj7rpQ@*IzlPRJT0K=11r?$?{EE8;LQ9tlPBjSpkYMs@ zCK65i1ABgXD|6H$@6^rqZw)t{5?59o$g}+W(`@?Mv#I+oUG2qPs4U&`&`K0kZ1uS~ zYw}M}fs?@>Up<;lyMp^CsE8nmH{&YqQZO60bB(lAj07l&*x8A6RUdOMc5&Z?N(RuM zI+;c2@k)wCIa8&xiO(HhpZt6yZ2A8Wd;XuR_S)Y@phc>^dxu}W`tbhnIQ#J18dQeP@52)>)Jn`Jv)7mG zE7QUOY|-QbB6uTXhj6Bz%eipgKrq6d&QFl|F_jV>a0I}M2>`IR?%35btrRME3q1V$ z#`}L%d&=iGTebGD`lH$_+LyXc3kVihy=R+^lOa%K?7!T;@7<$3PHD`hDP=Hj$&AUr z?Z#*+F#2tD97=!leZ83+hxKcOzkar=dG!2(%3j%Bp>lsMEo@KM1<}U292CYtMZqHn zb?^<4a)l=TTA5@+W+ZXBXd<+eM+*+m#jT-8Yccy)J5cE#5gioSJYyAP$d%5cq3}yW z?*<06@_sX{MNd%WgAi&inv5$f^!aa^$Ge`D|6!=ZZgzW+6A)*KDijMF%ev!og& z3Dr2uDaRbEAt9-dBB60UB*q~*L_W&#pMr{_vgLd@7KFs0cNn=80xT4E`X-06NGA#O=IaswD_Sr>+zKx zb8M~{d#r?!NMo&^ToBGkwe)dBux|-fe?l-pyU7K8C+;pMs?aou{x$piFfOMTY&>xxg`epbUP! zCG&Pnw-+sodDgAiHCEmNRzXnrM)AyKMRk)y_x7oT`p)LMXJydj@hUD102ImrKzaxv zSV$3ql^c-2Da+6(%X-fdHS~vlOm|=VSp6Ip#c?KHjm751FaUI@_S)^d#Nf%4e> zaF^69UBHl(P?StZrV;I9ZGd2!XjjGNHl@z`AiF$I^)9L|IVVlrL>PDPrAaSzl7g5q zpJ>VJkI$w{DfcPVO1?X77G@LE&w+O9USP!2V10ueRZluYki=kPO}IcV(yJ0GLFI|k zQr3RS+U8FL6zsra75)YA>JUX?!tNrF%!T+DFc`a1l%P73P%P!VK3Ghh1fR!0vwYfm zpQf@l$e1vt0k^G=uf^QE$h2ajB?no=1Qkv1PZ5J1I=S^HR04OLnv2_60i6LinjqsY zGKo%2Gy+LQ9k)aIj4#<6kxBQjt%DD1j$C~(G8OUl8egzB{I1pL zY{A#-6E?LGPallVw|;G#K3N<2*6PL5@Yfq8r2~fq`>w@@b8=kY6|r#E{LxKcxVY6p z$5j+PKv70hgAD^@7v9gxs|l+)6@#+g@bt_?3?jeJrM{eX!kEkg>P-)ne7FiRG7Kb7 z3b1zb%roaM0}z8Hs>EUmlgeO~JjN$G1Lja^IJkFx${A6F>~4j0y5s;Sg!KZ@7A`o~ zgbEhjBp#VQb++J*_BNkofWQsTQ9Hx(<&@puCisS70svlM zXC`fgfR&?ruaw|+3<-qH>oIOo3=Hp_?!u^PGo|B{&np`zA4(^~60*|S5?w3W*}`-_ zfdup(6iYc{HZi`vP^!n#7hg!Q-WXHVB2>pWmJw*c$3%K#(mU|t7th2H;rNoXjASh< zwgA_*OiV?+Q_iw3j?z^E3Gr#lSOmMlEhX26R~UzkCHCcT#cASQ6id2*4RYD2=6A?T z)yEm;cptB=Luq=MJ7p#FL;}_)uci;(e&x?mld3aZZOKOo-7=zW9e@O}St&>-OuWB2 zfl)b=Oo@C+A#|{?a=USl-#99rSf1!mvkb+WBZb+s~Dx?drg-WRMU-Oy4ugFuy6G}+Z#)PV{v_a)0M6A=+{bRVzrz^oYTw= zif5@Pe(b0U7uv|(^KC5=8@yk{9EP64Pv`d-11RTdyf+{9?J;N$JF=(6seD@%JNylo zBLb*{Vy64iGf4Rng$}~!Cc#MzjD8T#!~|xu1iV?!=rN1Eb~tRkls8PU<&tIVFiBh^ zkbEe(4CTQ!(+U#!MX+O_Q@)Y~T#a$Pu^26|k_US;Q7LNJpXY)}TBs-*9J5~qL|3eb+jQs` zmp8yq%;6BO07n(h;$RTW+YW6ATZems6QObhRA9lC+^vbaRtbp5P!}y$atySOYH+qZ z!#{SR34WRzsA~bwWD{1wP<|JAZI%L-Y+=qdv@_hQRDyR_gG=!Oop<+JhQkzIv;iFk zEnozEhgABp^#@dak3(d%#RPdRAoe8(j-o~>&Jxer5l1$_c3Qx6PN+8v=w5<8Bl9qPAc?%sz#cT_dE>~W z8+S9YUess|i_#7ORAjy+6N!aT_?Zi?@O6bmto@1SY8aK}yJQ$fXZqmv>!NTdz)YRU z*QHqV(2ra}Odr|(>uwdgl{+&VN%8cv1(NLWhdCrEb`GVNkWEA7OzW%A1XoUF~f(%vI>0oB)6AO6y2)u_ODCr8yJz=TXq3|3l z6`C!m1nuS>#~{lffDf_^;s&8GJU1v4ePB9BqynYs2FD@(58V=ng8HY4s(bfu1Of_Z zxVO6Sl(LW!+eb#LkT63{WWuI)IL2PMipHkK2yEkv0KPC~Jjy^s4JAUydNgxXIE0jUz}3dn z*o8Icxkm&}xoMWUp8e|L42z@`pk6jt%V$S8llSqx%^Q&N6Dx1 zSYW$=mK$i`pXz|X!su3@0vdmmyoW~=u7v$eDXUo-SLcu)kAZH_+pm4Zh=nV=2LoJ% zZK*PqF61O$v}FtDTnj9wL-H5ED+z%6QYAssH}*V%A6W4e<^N0I)+2=Vh_2%1q_0>9h~A< zsBwW@YbSMPI%v_N3=LaGT)};4gnzstBXqA`o`sTfE@tek1hRm$h}%TNZ61%r*~h9N zTWs74ObIcEq2Om3N+{WL%5bxIDN#Cx*yaXy+=mI{spuHsa73iabd~5>IkgII=V0GW zR2bX_3tA1qHY;F30hA;kdaO!L-J6%c9sx3- zysOB#w>1>9;FPGuB{Cs$UO$Z!&s%h~-{~ zAuuJF7Vp3Zw*vo;@8rx#P!o;tYA46 zLC{M8&DuI#h!P7015KKjDOO9E8)yYtT~V^473}MVDvcoCE@9>zNkT37k66KcFJp&kIgwQ~@(q)c-$yMu%<6Ee;rb69+V+I^n(#CJ!=otS1IOSs&g5@t@Ms`mx^B*bz`uC0{{d06SX?!#FZ*UsP`Ze zXe?R}Vwdf_s;!;m;ihq^?Wd~YEnlnLJYn%jvH(n&4n-N!KqEV^^>!h{uAL>nxg%bsXQ#Zl#HV+j>+Wf9( zmg>M%wvQ3#rV6i3%akD4QT!>sP+1mrI-7@|d8~@OoRHm$tk3}reIxcmD7dhtYcrX> zDTf0HS;Jm!6j9DI!60WhF;vTbXCnq`vi}09zW96;EIMtK8Sm4n3McFz_^}+@D>$aU z9W|P?)>bZFx8sER%S)8pb7$oBV5yYd5Em$p+x@PMSsY0YeOV8+bLlE5=Qn_Zlc8AX zmOH-imF*k7_K+ubh-`IIZ+m=AEQe1~XsfY-anV~Oj)v8D%-b2x7 z|J0j*sKd+Y_*{GIruZr|8_ugAi{7bB5uYF~&%90$LdL96vP~0?S)%!;;a!67939@} z7QYj7+!o-3Pdft`*1R(c!3^1f44C|C<>Xc{J&Bj|_##kvandwPW{L<2!0#L6l>374 z!AJ4JG_MvR<)k_yL_WP4`=NbGDaQ^vX41Ce%m?`_qN*t&mxXlCvHC%1l^_hJy_FZO zG^Pq2I7uq2lSgM_w$jqBc6Pm(qFt~{rqoY;Ojit6$Q9u1dHSwf3F`ekO55waXK5$g z@ca`}hwl@c#I8|B8u7pC@XOP%{0wEwjOeZzad`tJ_Keic85#KwiDmg>ky*tpvkJ=- z@)`2(=`Yo8&T78+BkZ~5v-YmfI>$fjp8c$M^RvN=&zqM&Gvw!tw#=FAnlt+y_O!e? zXZ>Q%c6rWD{)@wwFHXCVMCTXRvtQhAe(`+qW#{r2ru@A3mU*9D^S;OD{m;$^+?)@5 zF@IorK1lv+$d<36+g4j|{~DTU8J6)iWDK?D&eub|Z&^3RVKy+jX(73m7BTrMrB)n< zQNg1NE-}FTszu+D-KP#N7TjDs^J4Lo(L}NQ(p8_=U<4cuim#Y3Fb@;Bc-|yf2 z-u>cx&+>Pk{ExmZKl=Y%htK(=4!`B+_^zLCj{lrE`*ZT<&krwtPA&hOmS3JlQmwm| z=Z`NhoLyeJx%~ab^3Ub})em8fJBfS##TReek@UxB0jK^-$re7{qICsv2{xb;g-N|?JI~; zi?s5MPt;5tZbXb)$BR2{bQIqs+|pl(vZjv+xAgt!DpApuque+FdE4t+I&OoOx~cVc zxtCw^ezo#79j}{fPOMD7(M?aSU!}lcXWRL^%d0H|@gzohb9#xhl_YZ#hBRHAx~-{bjOp>nR0e znE+kal;4RmN3Bz(rfUQ6zY}G zi3-9KLWx+@86ZTbSAHszsQNyQCNaNHV??>%KgA2xM$TE=1aW7d?mOU5P$c#Dqh2UU z{apHR_0!L9(|6Y_f1i7LaQVl=+uG%y-@krBGL0k-fV1F&QqwHFH3yXF;$rotW8gFn zq=w{h+^1uy-5l(Ou6Ch_={Opx1aEF}i&8KhPnYI0O5d^}WiD;Cj{@=Z8>5*1I)x2B zY=K?6x8;_mlNh8@csa?ULs4ob*}}S%a=NQSRc|K6F0EAbyhW#m`%J29cd7W*uFlmF zGbfm&bJV*Qch(lnr1@K)lX}{9N2hh>WRNy#4OR=L<ULVFErCVT`q-Es61t{bBp6Ew%v8_d)jg( z4Bb-XAxLqk7_C1K+3y3e<$9&5DoGeg(h{-sme`H)vKSMF$%<0POFi)yg=Fy_DFOWn z)=jjaJ@?#X+1sx)e7SUReX{2+^QWD|>#H!mD|dNqfUh0E)InrT>qD`w&@#U7qR`&z z-O{EZ5To|h%FIQ?s9k8=>#U<~o4QaRWP*lEIw57Zzp?G(b1h z@Qy$XOzr4B^yUdP4*}P2&^ZPe?u;^wCgvQb2_^#{Qvh6dlbeTlHJX+HL=I(U$1_kGmG;C{feMF7 zf;vsr_Ta7@nsAr61vSzhrOyT@>h;J`6bazqjL zOy5N>F2=}xu)%*i#X_s7#~M!1(I2g?kIsNMnpxu~RAiTpFO=Y}}8>d1zZ(S2T`JLLNAH&kC_Pu%5Z{dbjvNAxP(HBcC=a$s6w-Q zKy$NJI0m9{@?mh9-7%Fqj5!0odm9!8_CWp`Tf_k&7}#@1#*z#Syg{56Bk4L6KOR7F z5KI^#$pmF+5H8rDLkSRK#HrA&G32c;OQC{6NyLJ z4D8+{$lE4}2N>${<`bIo&T-^D$WT$@aESVZh+VlEus1Prn6jM>CXFa2C7UJ<%_d5} zPQ)?2!!1@tQ0*fpl8*bY3UdYHU6cDml2a;^)0Y>L+0>LQ-ISbW1~w=qFE6EF;lQb8 zO>i3EP*Y2FQ-$kXi}Ezr97(NePOTnHtyxH|qn>EcJ<;fTqAC1DbKZ&8<`dTkPuy5I z!KJ3%(oO4dO}i7G)|Hobzd5aYFs)}HjYmD%r+c#B_2fYK$-%sn!_6n351t%dILW7` zztl}1cTIm2o<5P6KG~f9VK9AaA$^*fF{_&~=bAAep0SXZvDBRLeK12}ck0gxRp5g5 zL3LB~QSEf!!_J{>vvUATC&;dYZF*ZY&exQ(Rr~I_U5=rd#;>6Au1sqv%TNiO1H_{8 zRpsomRF7u)&H@BpvL-B{*+YacR*qxZvE>nIMr3?0Vz6Yv>etUG6nZu3JlP z;85;?#oQpt(;*vAhq|3Ua`bd~{^^L8(`W5T=&{q$_fE$W)&<*z#1WJ_v-8j~xk+w{ zxBS90hVl$4d0B4xxkvN!^79K?^3M$A7cb`D3Qo)+6o@$FlpQUo%rB^FDX1PQs97wi zlPql5SlH-R*mSh8$-iL3s~w|an{I3bS{>b#$_jOE7iP>B_KKV_HZ|%4g+7U))WoOdcE7!x2vtOe`v zMLX~i2PMTuxrvo<9YM@idMh1wf&pStK`M2Yp=BtDOtd80j)kg&_GRdGI0PuLqYg{T z+^+%)sqk&y`jiXBp`_`@z|0C+eu4_^92!+J{sZGU`bBEo( z9jzqP@5G@t=U<7takTuHl-RAK6m&q9xe9zS0KWOI%8h(cRqJBo(nYRR^)0>X4)^Li z5!GD<)%RPgyN9cL{vB?qRLx7hnsN7wj^+ zHN1Y1_m`Z_@AD0ro%rkd_Nx%|kLTO3LJ*R(`E|bif~sY&{8b3Ljrw)IZRWOoc|UmB zrJ`q3>-;DFuR_qa8`qJX&9C$AKVAqzplZIq3PHbeHV9M=Ip6-f5cFR^Y5Bhus`kG$ zX{lnKEscda2lgM97#~|oErLYa8TysJaYj3+Wdg7%r{VDjT57K$3Oww<11QNA-DEpS zNK6&{VZ_FDWK?c7e3Vv-6;|Ww#L5Vr(5-;#}UUHQr8-CR=G zg0Ym4GWhF!EB9w(wK)kSKzWWUisW~hlJdyt?Tw|PN%Z(@)||L)nOsP5 zZOq2|XSGWMb$|Io)hbqxhsIr+JIj=9y|5Wh&}BSx8CO-O(9-ebUMv zP*E>oB~1ipY({xg&9~lnD45NuG$^-rxOabh>+GpI?bqU%I&lHB@!aO?<%VTSqQCi2xhF#P`8GTEixiA}5ZiFO z%`1S=UhOJ1zh90zYxX(Vx< zm#8;a0-vljQM2q#b)PGxKCCp`aKAS_BIn%qYB4|^?#n8eD^s_*VCf=7ac`J&62-r= z^0RzgFg#a*%ud*bwQg8g0VUrMeKmXCf^vGc(TYU35E1~L)b3~l;6|==$z%Ae+!q_Z zX7Uyle^^)>0B#pDP-zjJ@s)Knv-NneR;r>}Lm7qUjvDq=!H1bc$!@TP0^KeqCceFXUqCFQ7~rx!6{Nj^Se!N!Tm`h_ z2e^R+Q2hx;vWcj#3LbW{QZ-wq-y){zrr;Vm9oXyHO+En~3=;6%Vm}wmQJ*@Ot^i-u zkg!+j`glm;H8HmA2rXQ+ix|t_p5%lUz^zDFPGqxYbDt{woO>IZ9gmBL6!19Rm8Nzu z&VlVMX37JMu{6iVzQb+C5iCQF28={e(9jA1)M0{227FWw;^T|cSxOoV)Ha16W6dtp zFW}(T-eZ=eM)+Q$4F96s4VK6nhIo7$g(HugZ+_OVq@FF@(e0^E59@v<_if=WL#TnB zRNY9X{yN{*GE)L8O!V9`t#w4S&i(CtYskIY{d)DW#Rp8G%XxQ^*@?o%Zhza$1y8%* z=v-S|ei)Q~`OI7Ew|dVOd%}Az7k}-3yZPJVBbHDj2WK;3B)!B-wrwnxcsOCEztqc4 zZ!A-@d1vXd)R))OSh3;ZJKJMRk2ykDD$Q*s9SWEFLvFu6ZvSv{ds0Pq%_xkyCcTe1 zlsRxEmlyY_@V&sMF_hECFA20DeJ_tnbhd{^D+K`d>evpwPHhJ@Cj##nJ{uHm)oD^G z`?%k@^4aJSxcTb1XL4IZD+Fz zzmKagZ)>^l(leWT?fYwbMoYJ!?dO7L-{0syYUv5<`CR<%x{=)KJCBlV=Sro2OjvAd z?K|BwSE2vo9fFEvI(EZ~?ms47AGP+c$^KGvY-G|y_}buI+xdpVA0Pa;T^oMdGv9RW z$Hy2;;Q7Y5PH8h`G$yl34@djjvaezaYCbmlA{@B!ElN8WPR0jCE!?Mh3$H^!SW?Fe zK8Tm$xAB0jD>5X_@UgT)JB$V%wCLGwCJjnA) zTSQm4p+r3WmZ2*L7gK2{lq;GnoERK~{kl`D75VM|$x4oCv{UG6W++d6{od&hfYR#8 z4`CSaBIHEV3a_eG+dI3(Gc${G*E}KZlsfJZe7yY2r_Fw|waX8+8RkJxJii`+yTwtG z9N@bPRRU}Jt^#;J&%!Vf^n^L8bDyotPIv;wgtG{FRI#^5RPMlj@u-Xr@h3WzBd^00 znqei^*nPpVikY!LHpFSW#_5E|>E^}hHOCnY#%*4RV^HIbbmL82?WhS3x(QCM3ERUHT=Np#n-e?-6Lu~nFsX^&x`{rniN4{9{&|T3790LB=&^w2 z5nvOr9a{LU@C5%!;U&t^4gLw6`!5apGbyty6am|bHz-S}_-BRJc)_t?(+at@oX*>i z|E}?|COX3y!;n~{u4j$ z^Rn!=8wVGDDZI0uH`*4z{KqQa|E=);PmGrT->2~Y^=NrCT@x)h4eU3kX+vt*G|o46 z%k{N!C`&3m86`6Ai}Mhr@)DFk$=*Ci?5EM~)Y?z7QVm0>ta~dQ_Z~mD7Hh&}tk4(D z`4LFTV*17BcplNnAz4nKoI1SzijiIs0i5hYM%9yE`yw-swuEzuf!}-bR`qTq@{20EyJzfrA zV0Dnl7PH=zK<$FWnIePT+Y}DV;8m~=Z5x(mGFYTCTG*+(O(xtZh`?fqO0vu}LfBy% zWe#{64C{@v+19xL#NN7pe?vBhRIdIvg=bx^;qq67msYMRvi`Ok_&y9o((KV;%D}?x z=^KqIKFwWQ(eaGDn+>%#!|LskIOLaX*dmACI9g=gcwHKCHW z@pJZPDwkCC4wm;E9+<=-o%b~(D=y0OkYTY#3xtWad@l}Wo_+A#M@zVlHR**2=LtU} z!M<&2p+M$h=;JNYdTelT{47ZJ*LvH<`Z?wU3an?K)EDo?GIJE~r}-Dl?PckTD+5Dm zD2yU}3Tqb%3c+}k@&#b4&NvEoUU4RG5XM8yV$u40C{cJ*$-jfmz36(^a-gB$OSQkv zMfa!o2bx;HTnajQ(eo{mq#piK6aMhx&ad~M-dOrl%R&?$&T8K`f;B-&R?lBb{W9olLQX$;{zmU>%i(n>3C_0?@7&9@ zJ)G6a6qZpcpW&&ne@B>L1KwnupRAu(1GRqv3Ws}6UZLaHaqgK;J2*v=n%7*-)9rhO z;he)RbP(mJH7I3T!Q}e{Q~|Aql_NMSmoRjJ)mk@%I08qxV8aC# z&Yx8|8o>bNuIs_;AY^yB9;qRF_m#Y@B#{kgL@c zo%yiP!;b@Y|2%oQME2vTKZ9m8z3a+R5t5T07X(voF{;L@8mYi@=)!J{3@4$ogA53B z;^7Kuk_446#eh}7fg%(-heh_{#4#FrH4dG7MtgtYD?<(d@OFymLwUEsYhEVydR!F1 zV67l|V_e^5;9qX8U{pLyRcg2eSTA>*s5%@C2F7z}U#?)Q(hzMFZb(a^nUJxHB%v1>{2L3DrEvtor?8YC{}v=kTer*(;`MJDf&8JBuS=iW|W-#h&vl2(I{V~p~N z@`+UvRn6RWn7&WDw0&NgZ9y5;poCO6108D3!RjgsaHoI|8Oujk&Reiw6Cm+J%}20E z(voyWyAC^qWXXzX9Z7i+8oqyO&SJaoS%v#ZR1w8;~XMTq-BDSA3T@?G>k{is6Cg}af z`MA2qi~n}-N-<+eONajp-Mjp3;P)f~82)P#A=czgfmg*pP9pr@kMp_s>Bjf@_n&S8 zIL&D;BoR2>j#sOmzD3&bVfr>~t~t{o>Jm89N%gCqxg!(yVdgF^NprSK`E=myJ@xa| zv-dTxewckgzpMGV`~MYfSMv+!GxhD$@0MJL#KG?~U24BtavMH<|I9OA^JA{x<=~Gm zgMPI?=AVas`tg;YwC3l+`00Z`7bngmbL0Qh;R}maGGLAtW}`Ga02;P+D>0+1Q>mC% zX#Rmo*5P*otRR3y0*EaPG*&bDyse!Q`udoW1gTVjCW;dCV3rhDf0kMm1c;}opxaIt63r~0|S zL>-A^#YC1;#MQfq?FtwLz9CtMsVH?(nV=aEyg`W8g^>ZkSOX^1jI0>+v9U4J-Wd*3 zEPSZ~dzGSzU^}}5`QL#Vv|uHHi!6B)R18VCBn^2{y{2*c>^kudv=%bBrwxk_g>%qq za2Hx#As*6Yp-)+t=O`n*?G6Sgc1BQ)5Yipv#K`koQvR*&LWHgB?>&lSHK6u2;R}lm zc-wSXEWA34l4Hl(a24tqt|kEFx@0uiVyk>5TPIM%LPmP+@}r2K0@mN={B8ppe|GOm z003Y{_#0#9dC5jOHh;KxbLVJ+7c9&z|LWeoGZ*0YA8Ydceh@87iD{(XD?9c@>mqbMwL<$nF+$Z(YxBh+wn(BuK#4s zk+Tm*KI%yPk6UsJ4{M{pK6vrB@WrL~BU5@3!U{oE+fMBBj0t5zFq7q}V*Qlp$f%|E z)Fp1HeoE8@isjQ8$>t>QqS5|aqLr*vm+qJ1e+^&!|G0O53t#-Rwj-7e-r2RDg|EF2LZjGa%b+3<=r9;MDa%ca_H%etcl}4=)Wg_gtw7d-!*4XY;{Z`rBZaZBxVP zhaY@v7taZcSJ?n?EKKJQojvltU~Qj@SO#y_31}Ne*>-eH8^5Y###nYpa%qreykDz^M?rN@lPtt8fHVLNBk!k~%#(r}tBg#57)AUz_Hc8Y>}H1N*<(LH zbA;O_%(pG|6&Bktl0J{h>m6!dmaPM|^vu%kJ9Mlpo}nH#7{?g_vC20pugibe*Jt9i z$5G@+)~%7-#Y0(H8_cmdUrKijE=(b0dF%{-6EC<0GJ@B{fY=H1~i4i z2T5W0JU^nv!5DM6AP9%0!Kn=507r0TFCfAK9`%z`?4Tp;IOnV|gN4Y=)MHRR0rCRD zF@YC+%&8!}t`|iaZ|pFr=9KKWNqs+`c1({cyB`wD;1h#_C@lJM`T8S#IKfX{WpB9O zJ`1=(O=$LMJfBZg(-H*hAr_9JyF8d=Kbv$!JQ>}a%%4V@)G_#Aw9Pc2RR_od7<;Cm zgcc5~C7|Gii@>MYDyAGZO98zc!|50oHuy3KP*;c1TnrI~aiU?I_?Q=L!7MmdxSfEk zfoV@$D3C#UChPCv3-ti~%0!ytK+kP8hKVWwka(5rYvG#Q)5#Y;4kJJ@gzj2363 z=I>@0pE}8EK56HYB1k8`REG~a!2?Kvj3Eg2BHs;ZHozVn9Gr>b1z|5R;lAY{ywp~; zpSpek;TSp?2ia%gd$CX^>YFai4oVUtC%s_7oW^YF3&@r%puj!JSOKrun9g9q3_Cyw z5_&-+5f!q9$#%HjtqziEn^ySMe9eUFPc_ouUM`NPl_fBfBUHa_13}2BF84@ZZt#3A zWRC={GWiVLb|xm-pNwN3<OhgY7~%{O9fRv)#s@3FZfvME2%p3cM>FMi~W27Q|p%5w%bN z&1EfSJJ@@?_Gr;ye$jAC(et6;dN)5YjIZc&T@bSmATtQDtiR{%j6EyY4<@i}ud3gwH%&aaP+94(<1 zlt{If$lPbX*y=a77dLOFO0El#OgLuyBotj&`XvP=QV%T0puV*rB`B2tUU(u$9z%0n zyv{R4l(0X2k=_sC9!IG+s z)vJu3+#4X}C#C`_4p#&%o0g|fRvw8sPg{F|0#_tNT*xc9P|$i|$C1jQ+{zP^z8OoE znGxYR#4`ZG_^>9^9)rI0EU(Rru#wo9J#0$7yN}j#yq7uuL^AVUN|}_tpBmc6>hRH%hE=OMwZ6wNG^G#gElf z3+tt>)yq7qm-|*vlWtJdZ&3EA6Y#JXeC8)CT|=^Oc;jBbTKe)@{ma@OmvxqpUDhqU ztat75hKmQcfAQb79#p;WN1DFW*TnQGy-a^_+4@g4}x27QJt0Di4^SK)R?J7&UIaa?p-lIA3SaWh= zbLxqv0u@(yx*29flJ!MoX<%bS>Beh~+po2pdDc?=t%W1qTKcTfyAOt^(dqL?9xtL4 zXrTYwRz=`i!?)H(>1$W?uQe53Yrb~v>a%OD@2_3|cI}q*^$vmScl58{^|;=3?fU&^ z*YAD1-YwA9Bi+`o-!`zaZP24_=vdqEv$p5o+MX>--xytagRhVDYHmze-IzRf<9*?c z58rM~N#Fdae{EzkDJ+jmFOijr^ zN6~yo(5X(Q%pGro-#DK$bspU7#9jC$!t|?eM*_KX5*Q zmn*D~nTo{$G!(TC(|jExG8X>_17pd023y>27^OM9|mf=C0 zc-U1u{0<%=ibovbk$&(5WP62H^}?Ha#dq{lZF;4mdZmkcWq$O^$@a-^>Z958DemY~ zkLuId-=|sBx2mmgwd`a1rpIfRcRXIZ|FL$|W4)rs1}7hHZhO3CE3f zll^8z{m>7{_GAC@_eb|R0#bFD7q~}^2;Im`EH)_DYXds|% zAaG>hz>k3-*{30!o`&vtdL-&;c+t~{wx^LJPosZ4Wyuc4ZW@f=F_;)Nm|QfN+BTRr zGMN5j&@T#7qYX8PLb+|2ayp1?mUZ+4lasy?VYc5qF?D2 zztX$$%3$=>=AW+^a^psu$Nz0hZr#u4BEk45K1!k3;gw_&%FSN&=dsJ4PUm(21UhhQ z1aNC@K3Z7*=h4{UpG_e<-v%M^&VM-?yL#gAvXBCi21c<^7_RC%oC7f3+`IQ6(X6;j z@QudiH+!PrB-u6{`8^uD-}Y^p+~nCmjmFl=y>Hn3zA?QiMM#0cL*8Q8XZEP7EWNK2 zF9L$zvFB}>4bS1jPAiw9y5#`-!{OPQ_lEU;kH+qdAR~`4rhlNf;|>8;?#|M_lp~Dk zR>m}%xxR6Ch<(@8YpCwS!>JEglj?{H{t({h4 z-i+}C67FD<#IQvu<0P@3QDujYDb`=jvbwD-23$-A5r&wh3+w}%h!mtF6*Wy^y- z0$+c%$G(cbg!lq3{zei(lFE1_1)6vIh zXuG=O{yfO04j0W`5~>TR#_dZA#VOL~x9I_4G=en4B`F^3G4N%VFpS_==sYMjP0ha` z5=NTNw-X^{3f@kOWs)o@4>IW9SO^j}Nsk0LnT?01 z#)lri-NrFY5K?jPpRh06dTRBa;r@4ymG=cW66LZG^;S+N|-{vO6 zPIHmeyiB9p!eM44Dyw~pt)HdC#4}JpJZwXv^9F;U;wDhZgsOX&T}IntGA? z6{m7{TRl}lC#iQ*f%aY%11yaZ#Nr;#R`fShM>^V8%f;kOIR@2>$&uB^7TaY)CTJ8& zYtMl_qt6!)P4t#;JHK;e>F|f4D+k}+XnXIH49tqQtcymF?N}et#ToW3k7Qw^=UOZj zn5eMu0Np-K9j)7*2!4a)VVai}aup>tDNP9a-W z3qxnOs-MdVvDCPjHl<$|*o8i+I30cqJC-`Z-LxPT%LTR?QMtL~VHrcp(DOz#2u%BX z*Mk^cO)Y?mrU6)$p_jFr3rY-6SQIob^I>ClGlVBk>5^ktnZpVWYDNss72sCz{7o6hACv ztwN0=8^9uF(hkiz~G^&1d9}J zSDs^`dha%xY@Kh2MkwXWd?8~q_#7dg-191FK&Fl*AZZ;$i&rbAmcQ3$2ri^-RH{&5 zp=CI%m^>m+-{;1+`ELigXo<)mwz+t+>*}_CSYXCP>;uLzRqINSWl?Q zvSmWLWUTxpWyOIQMQvL2V!}e|ohryv?=<_ODs9rA`>KTpL?UR`tYbi~#b7;z59e5@f^)PQfwx z`LQI~tQZlyG8WFl5^KjW#z)t!z|r~Wy$=ux#Y${cF-bhyWsCz4C6V}s#QW5k)o1uh zYN4FtIwrR8s-s)2eI2q3JI3KFCb{ygkWI37iacnoq(R#FHoQm-b~;XSNc$o^;-#XE z3=LcYS*P->-ad9C-nm0daLc%nlibshRTJ{Wx@sl#d zk~V_+kzC-kqMCB^1TYfBVV_}^* zXcd1$!3eu}Ri_exJ^seBOLhrFPGlG_POxSVOc5+4!a<%k;c-~=Gs=LJe|gl0$501@ zf4osf%BDy1j<2W#2q8;!iaZutoq>Xw43r#f7OHasHMN`Tf}gg&nd4JwYNLihrbQPT zbFv-F1Dr2KI9Cr;MGEt?1XJxGTc;A@4v_0%O~@2V;|qnu?KnNgMiNymK?%6!X72K! z;LN&^b|sRsz5acdqO633VXp` zaQGFKnU%!gW(a#R@*Nu7Cd^z~AD184u3z{RT2Wc;_prudGav8hYh1gFPF33P;MMLd3{XQk*i^@gB^(mr_j$G2So&Xue1-g4(qe)b_b2~d%fAN=O- z>A3-&HuVbth>Rm!`Y>(_RdSK{?Y39PH~6DfgHvoGy+z+waaV7ej=t?U01S4nRwppx zzl0_c!?!D`$8fO}IEG}xK#Alr4wXcVqu&pfuUPgC$u9ctqrM>tTzMeyL|E3%R9^XN z@e5kEynXzbIn#sWN(Ne#?z^S^P=~$mp~8{xzPciN?)$@94$a-K3l~f;ZERX)`9X)> z2}Ee^P4q7jjPgesi@hg&L(j?n@V78`@5` zO;mu>dG{w5eUZ1|KLT9ux({5gIIRCAIq}`ZVw$B{phvoXjXTa`@V4ra`jb(CK58CA z4{WahA9!~suD^Yp6IEZk*bx}$=ke^>nu; z{;X9OB@Wv=Z@N7-N1#d^6+Cru$p}tkV#a zMnjx8_iWG4sdr9#>+z56{^Rzm8)hZFTX1yqqm%jT8=bwOqAtNYmgVxMqNK5<&Re2Y zi#L?Uo*i4ce9N=ve>RNNTYp@4bj$M(`5X6lSpT!-&6bzH|17&YnKZ6&&Ko>rnR~-I z`BUlg7Gsl_xgE2T|1JOCH{`NyQ|I#J&-2dy8VQ`-)LoS9y5b)_yl&g(-lI0o^%pDO zB~5Pbt2yxXT*=lCtF~=vzn{FN`OVgUwob0u_C(DKMh*_DrPPDad-K(^r??c zhiAUzJ=nHmd`+}>k0Rvfvp4zQuTE|qFIw_z$a&k3u5I~WjxPEAe)+ba&nNRIZY}xq z`RKOaAGU4#^=8TB&o|p9r<~p}S{ig%6ZEDjs0O>%{eC7s5g}wD#5{ylhLHCm28{@% z1;Zx>mNbzp%BRhs0PdDRz=`4#P4SyUrEfy$3yfMB%@axk8*(AK;mYRp38+-O)ZOgLa;$q*W+#!3{d#3FijXm(b9TnT#hTVY4N&q z1L0Fcrdfq}x`lWJgm_1W_)upW4xgPgI#+5;)tU@zJ?^SZtx!f|y;+uC11dB=A^fj8 z&?-mh1!IMyt+qL+p3DpIf zr^-S>x{Q+dn4n}4x~z(1XX)nNEw^BhylbW+kwGdoiGrZhs!{Ie_Oj7nDldF#%ijjH zV4Ma>Pzothv_-3rSVbpx?Y@cLn7wZnMN?W>IjwY}-0h(!S8r|Xt|0EHMK=+2{g^xRCby*JnM{!CCUKw;KpkjlolW&0{{|GT$ot4GE$#MEh^@o4p!$jKBWU)KMuB@4K!Fv>ZU%ZH05#}9uj>iqYQTL$IjP@` zyR*Cio!q|1 z)l$Gg0`eNzMsdnmn(T6=Er;jozoPUm19bMY zMHc|BZ23(cx=aKmHi0w8p-dXtG;IF0m>$;Eg;`w-S3Q5saz9bX@&@^M304rI1HAe0 zk~;RHx@z|c+1%GCKU)}J;?q8XF1gudK$=DAmWSv!db#(xeSjf~vT_0AN;GeM)T=TX zoB`bJ0U~JfKTUOa%g{+G5p+St3(FrHk-2g1Zk6BV$#4mA*wZ?kdm(Q3{*XnKWfmoYIN3(=JYd(3Ig#$@**O z%#lrz;+LqEQ6kW36fZ?W>(^}S*?bAAXuXK|8S3A zc(=aRk#aRmDU=( z@)R1-^kD`^7B~UezJ(agu;B#CjUz}nvPBw5BvHic@YM0l0GpNU)1SHwh6}SqJ#IuM zptRr{nMW0x$MHiXvZc9&D^bEwGa4=^Vr!HHK(>GNzi?E6V3diwL0-WIxnweZ^ zDuA3c1!e+}7DuoXLmm=YBmi2(!Ba;eeI{^3$1yw$j+qzLm4*1neA)y)ArA7(kcD&b;-*x)QGDElex-nd zIb(+11=5BB9(0ci$QKA;J^))W@usR*%syFI6V4rt3yUiV$i-8QEST*;);79|83#UKQ_GvGB8DR)LJXXYxOI9L%E#DTKa62*+>_Q@qAWttSEdE-M_ z#1+kj3sm|7V`U^{7x4+=N`W}}y-X2|Lt3ln(I%LE`o4JPCg zit#cpR*^ZsAiGJHnG478@XH>_Oq!J~%_tc-E1B%`TrOt_z>P}^lf(oMj&k-qbdFBx z&zC1kre+V%3sDWv+N%&(;<=-W6w&(GZWlxoQHNriklaU-KIZ z%#AW4eM>QhlEnH)wSXd^rt9#3_JLtMS!grNArU2&3dtjA!miLuRLGQ0iJ*bRm$J1R z7MO@O2B=;pZr$i8mZzu+DSC5r6>f+&calabTGU-|xu9@mcWI&Hgxt4HIWPb9WG%w< zHlSJhXz**B=6UeIW`f*=rC&1$bDIlSBMGr_1?neqqGp6Hz$Z!fhVvjA1_qP#9zmI1 z?(5;XU{W_4!XbDQWji4iL7Xwav|{jyCCekqq_nrT({k?V>BSEYubj7kwvA^RAgjln zvA6Mej9-guJL`M9nRW+Pr?T-NWUr$Covy_#sB~DNYYmUvA^zJYviiXoXd}TQthIkH zpLL923It@FpPjHHVkJ8kYMt{J$D7W{0zQ>8|FZXf(74w0ZHP%?ME?F)kTR0&IIHy$ zR3|KD8ahQi$~+?%f)QNLNs|XRhuBpj8x5yAT^GB{N{Cb%)DPct3L{uO|50%_gi3~y zhD|Z=6!tMyANxu|-=gw?%VsnVN9Q}PYb`RJy84&k-{CuZz1DpE<@aUu!O;b$SO50^ zHr9V}-9I0H&zKm0b*p^pnm@n(0pFPd8O9t1Y+90042yv_7poZrZEH_KxY6Ge-6iyr zNNp?(qCl{d+b})SCyVtj-K42mMz(AGqPc4;IHv&#ZN1Sr5{n98#QiZhimA$G&mENK z6}nqCZZti!s7Avl*{XhNOPN+{+k>5^Tb&It$liz2O(H5EJYY3&>PSvCy6jS>X{4H{ z*V8QsC(jv{PImb_>rCd@dzB%P=owB`+98omAhhqW z8t0S!Mb_$+UoY!0D&S~a?Y5+~63k}NjubketD3+XcbsrHtu)ac=9-=13>6EIvx0iR~inR}P2^_X?%5JBBNH|CV}eOn)_b z)2XLSCnHvPV_<&PfVh83b`}Vk`<}yY+E#X^pti+LvN^V^`O6&caS=%*YD}!tGlHFBSw7Kll=i^j355 z?^G;OBEK@Y3PxB6K0B9A%aFi=l`rL}rqm!Z3T99tn8laj+%hQ6m<2#@Lh%6-C;@%N zJU6-*eS2@};tmbdL5lvJP6L6Ay)hZfT84fc*}SL4!O82`?YY@!%Mbi=IM0)uddZo( zg^VSCJGcDcm?&oq_Dbsh0r|iV|^IB+Gzw??<2Sj~z3uHpS*n=_&@cI!Q=OM6ZJCu331k z$#>a;zkKkLgUkFSl|~u&VSul!C}99MZ@*a6c&VpqA-!Tc-bj#r{Net8&m9f=?IKpV z_SVwRH-rbeMwcJ&J?we@SkzY6m_x3Qjzyn8zVNE+!oQC{I+^|GID>UB^8E7qRfLKo z%K}$=JdC&jQkPeTOo}l6GXVX(z;fLV-;{mSI`Ih=P3Wx%&8N z)Yo%f2i{cym*~>_d1rSW^!P9rf%|Q4_kXS!Vk7RcPY>Y7kL>x-v)ng+)8j9C8`g$Z z^X43TW%Fj=`6IsPSFGCd@X_;sFI>F#+g+-dGB8NL*xVlIkzIb`VN=O@ze`=K78kiZ z?|Kkhwc+*3D^|e4bx98^$Ux(znAv^XZEl@9_`5OS$&L8D2fc|ipJ!3et>{$#S>}5v z7R#Me(%<`dkKpw0u9cu>05omJ2tjGB)vPtj=PM81N$m4{bLqF)4&?Crj(~;V zE1qmeU_15m4B-SB zugg6<^1bU#R;7RNGw&Y0DP49oVD#0OoKBVd=PJrwE1wfjgSg7yfZuKHT-+Gi=i!MWK)yG+r4r+9qhT_xMyiqou#;h@jo;)Tiz1 zT83;Un-%UJz;k)au+vPxI}1+zyL)Nku)}h{&ZQoos~)zDOrLk^E?N3{|L2xBL6d&n z3Xd_Una(*EerSHu`#U-PQLcd;Aw2a*k=* zvUSLR>Wuz}r#1{mOl^4Hzfw;xSRZxozX4j@j2E9zZ9H+ox?#=B69aEf-M(C2qu&)` zFHBEQ+_c=i+581_0G^r~8Fl$-%CdkFuhW|**UfnGLQTvAoDSZ;^JK&4BQpcv*(O>4 z-JWxLa8t_#JKN1YOFYjVoBKL)!RepRKOeY#!r60R$v;2(6bHVYe>rvL+HF5xcwPT- z&vyJuiuFFSx1_NB!V$l~{pm~Jz4@=1nxz{ilee$x zTbFvJzJMSd$J27h-fTLuY{tyV*Du#JF2Qvi1S*!CwqZU6bw2@y(hUJ_OjzC z^K1OURq+q`LQS^d8F)Ywztxs<@5KaHS81V(jJ2fhm|XDd`J93I*iSt0^OGgiTK)S` z!k-VeKfb;U|NYYNLh`puHhjNYFqwjX?7n0WSO|77rmW`} zlq8bmH7PrFYwMR>Kdf8ZD49rK$UeS+`aokJWY=YA2Sy^)%KJoZ^orjPfehxxxA*vi zW&~*!1uBB(8hr%|H{&Wk?lXlM9GIuhL>B1q6@FdD?*72n`0~Kr94SjD73pyl`+bBc zAZ`05MhJP2sXfvB)spjg2FUK`&&41P3L{v~$puo)Ttv@*|4I;%hb-$3>VpOPJ1k%X z-R=T>{qzi22$27^lLCc5DjzN1I(ZNaTndm9acSt9%KL%S7p6HP#0>4F&Ys;q_WW>7 zqM>X{07fj{<50bH&FGGlcnmgJnthJr{OTT&0}Kf3L_5;%%uc07qCX-5NBwI>-7=p; z&UOH=e1D$FC;6i-0hjkN9~so?RU-(Txyr1nDw_ILy! zrlIo^$~|4h@I*NLZmIW$7(55vLtPNpzI!@E^d+Fa8@PfC3(fwkTaf+IZ%;)?Ah1Gv z_-UI7oby!LZBOVMYS!6<4b^`>3`zWvnSTxy5#kVVblCRn60ouY zCiPC6UExn3PbSv3&fBUXD_sw{V=uqer1)ZTR|mBr&pysq@FJi zpU;}QVX5kOG^?Zn=wrb-nESaoI1#pFA$=JhnX$E-TV?F+pMo4_KnO535wQc?Gu$yI z0i9-m5xdlzs^=NEKL9F+h+AnZkIlN6)E5@Nl%#K-a{x)G4sN00box34Wz@Y{BMnu# z^oDO;NV%tes_9zOId2V6=uYy`Q95@Rp|3u9#nsR0;}M=PDqS@&TwmcT~{ zzzXhotIK-Reb1}nrell#6n@0gk+*NgOmvKU!(cTUB8F7^kBY$E(=ex+3vW+5-g?>F zwI*9=BgO#9J@LOsa(+97gUIaPM?Wglw@mO3B829%mBV-3s$1CXg; z{rY7`;jHC2DrCS5$>co%Rn8Yv;x`?4tUhvd&Qlg{SZnSOiR-oBN=K0A%i&>XA3Okl zTaI+KIgRVXt&e@fqHUs5OIv+v-+pU$=_nkl zu_M-qLueVOM2FFG#7B})ifR4_H|I0k^Lq~HS(Y8&z) z4r8v5!~unbHUy|b2qMG+20P9K&V#Q{wk5 z?4Hs8o5mxVC`r@m+6fRxX(P?I9_?b{%P=>!w%lM10YRV!o+BTF#d2E4Y^ocof56}v zq(1^Bw#7Akg(?gqGXDq-sfgCDSUHNS#lxMwuCV@eV_9nY(To zV$&qEA?f`9O7ck$AOCW%E}EpzeO5QsCm4v21AI21qO0+1I^W}@Q)i{2SUyS~cTX82^hf;$T=1Nk|@E&%8dDRXVC2L)OxqIJ&_ z;>spb7Zw7yVZ;HU{;2kNjHY)=dxK^js5^oI2xhGU%|vqdiePRWuzy5r+JQc^YQ@E2 z1QPZjQ%(y2%vo|vv2YNFn!gsBB0?LT(56glbDlU=6in+zxs?#J6jV3edb$CF%P`Xb znL}c*i+);CDR&qUng(bctmK)MpQxQEOC+4$0QKH^W5tqNHAFd0LIgl--HFn_G!Zy} zO_`^e-YTC3*8W0(VU%YjH_Vk8@ZhP+<329orX7G8;At*JNW&mQrFC9Oa}Oe=yN^w=t~E0}{?YqX}`T5Dj^6B_^MR`kg&QDLe?Tq+<&49F-$ zh{HnWyx@{5#4E0qrja>R1eK?Gz3fb2W~7?34C5k^_S>Cc+d zmwemBB|=h6+dqlv6(b(q2wWz&L9hgI@NTtZAzzFhQfaO1T+rxO z2N)UrN{tzg0q$QgD-C2qYcBGUU z4Iy@0yQvkZH%?|z6yh{0_XJ=|9mYoFw;MpaNP$5gX2N3N%CITjkhxp1-kCsmlU>Mu z#?!R2f$z3WS~n?Z(YC{+QR{^hLb(_*59RV8OEtEzN$AT6c5VyyX`U=G;K`Qk8-{ZL z+eU4kDMV?~TD9!>Cl~Zcyy1gePw$2dDmR#qf%`bEPK;JU8DfdtAZY-12Vvm}fN{<= ziX^k^l)EaAvygXOhq>!dVD1ROsQ9M5A$LFlCmrUL(@NJA{%CA<3vI=5P#bE9V-{wc z_`|-h)p`Q)4Gs2CBR-A6Zdt*6oR-;%I87LFdu3C_LfSqzs4g6V5GEgnENYuK9 z$};M;45g42Dl{(xX#bynLLB`>Hg6JMR2FO<337l|!%;2FgE(Vyj(9q@^a5u5OYS%e zUy0azLr!YMWgkkcxL`9NG|6gpii8-6u(y>K$HLrV6d(yC=>BzWlzS%1ZE|IXxrntI zwJC!f1^^cA;@UTI51q_FgF09tfT4EXqSl;pt)mpGdnV^Lf!s)FDk3*&0xf{oE@2^b z5$w|f@>%j1845!YIwuST^ZXnJK<6xoWd)Of5XLxWg%%pPtwv42G+v1FFPVcx;500A2!P1@y!BHzk`?S;CgTnOZruRm?F6xY z611!bnNL7w3~b&QY}X*y>+bH;Tp%T8TY+(q=?0ZTCx+Z05_G1X+1;?^7Qc*>1KKWc zwG{iA>AUKP)*&yLob^NR9&-;6k{CjU8Zl8})~wIQN{rFqXV=|Ir%p5I)pEz?K_r=T zjKZgx=)GPz7E>$yx(seTSmsTe?p(D_?hs8>5tTrP9a=S*?T5`P} z^k?Ncu6)mQc~ar+6TUvUHu8xp(`zK=5=e?xbJ(29>a$1NtsErLyoX;h4UZD@NFN66 zX7?PK-S8sj_GeK+5k>Wmb@>Jvf6xfvA>E%H%XYdPJEG=#y`D7StX|&%7~E(VX^^~+ z15I>Qo8_X%$BQ#@s@`f|ifDK~j%2AxZ6_(b@>pwAZ#Y~JdBHrpRs>3@>NV8Ll1E`yvNL=1+Ouh2*rqxu3ypGTv)@<%_m=g3f zw48IHJC({j1rEwskzk?HkMJpV7wn`;+Y?8vk}*?wegKZSOA4y&FRxf?l+=%gSImu$ z17VXplG*|@BLTWAei@etL=0%c$|%w}hswnjRrPmE{@JpVre!G;HZke(L<-;>b7zDxZET z?#5rs)U}F=L@8Hm5UM7%nZ^ApIE)LAL60b7M9HCQzF-qhjj$~m`H#wi*l+81Cs)5c zvOk~wuHo>3>df|prK?o>t}KD#@Xb5@ni;1XqlNC+)K1I@8rSTK8gW4Us`vWgNJj5` z(#;)rbOX2r(YXbbn3lT?wrvTDJ|(7U!mNKBhzGAd%M~o!&Nk|Sf&i)3AA*SVAniehq%9PNk5X z9fZ-w5P_Pb0jbCkHF2Eb?OF>tHDh7kb9UpLkm7KT!V0fZGH5rOEXM%mNnMz&NTv$a z0QlmD8&kzQp|x!~+MPQ9Zl75(IZGF;&);&{)m=Q3F-SYyq`FA2-2u9x>>4p-oYz#w z=}1|hD435X)x%R< z=K~aJEjuMcY1NCDPv1Xiu&+UAcxo*^nk6$6=qm~SV2Ll13jY^TU@+aRa7Ck*q8gV| z89NPxy;{CM zLD(TCr**ENP})Mtd)8WeSG6+cliJsFLQ9|i=HlIR=MI)&pu4_)j@A?iRG$czxD@@g zvE8!r49-5L6=su!?1>>6OE(LtTNy>$fq_hcltlT3pCbQcWyIm)hblv?H*`aW0C%H^ z2Ng3lcp5_(rblqY(>2uT4hjMd(pgUZgqzt|XY}BiaMER%U*v=Yo8RAwIP4IX@||jl zMc7_~tF$Pyeh@NcVWd;CGCyf8ErJJIRwAyPIL_SX8ZCFvy6utUb*9Y?IM}t@0GBDG zSjC0eHA?|udL8b_L>t?n7KK<6X4a^JC33)xm5GVY7#eu5Th7cgFfPt`Pl%Kn#2kx& zti(9a=5D_zme6!(CC;WqL!u+Wj0NK%hB-2#YoDalMcvNaQIDS#l$sZ5P}{g#*h7G) zmr7ut2(3Fe?*x7Sp>Vo>9&Jlg9J;&W2{}qFGmNXlRk9RUcTKyMt%R9(#6o9RlVaKz z8QYJKH~){eJ(g3q^M?4AWA5a9vac!yWHw>e%)yda-jGEl@5a;)Ai|fFLVeQI%JAkE zB=R8BF!U)WXV7WoK-QG)G}xI@s|xz1-BUtZ)^i|!spU)7Ex*T~c5E6L>P|ES3YtAX9Qc*xI3Z&m~~kl;`JE4hp(37!HOGHx{OR4&=C#`Ru z8mZC20)lzy6}fT6W8$%Dl<27|TA8IFm}6m6Lw72|MrD-5amdYM{`S}}DYzK)rfsv- zVDRHyip}_aw>(Z^h%UuU($Hp20_h=5gJ!8?kEdYi1K!bl@t9cdu}4Tk7%4j}kYu;0 zGszA*)Z(MztT`!X$Wfv42hF@Tn?x&eWMwVmuqw>{qlOX{qcAPv3&CkZ10-t_BG%`^ zP8}T2*wA27B`JjI|EVM?ua5}^t}NMF&;H!=&KE(yB1}DIQ2@X=fNR3$ znloiM8b}(^P*-b=7=tEdB5E6l{Ax9*H;h*z#B&H7Rcn|d91I8X$%DA*5;$^BCkucO zl;W9U*2_0Jf>4!+$zzRCK#F0#4$hJo(o;;@=UI$kuMcQ-e5{$RGgsG+Umj%V?4Vq$ zH>J&I=b(%2WTR)_13Ny_N^~lLng(eJ9i{Q@9Me7tGasZ&ITpyYO&d^(nqxY>))Y-K z+k+DN8cXAV$8rsIGLJ*vATeXg%rgMk8-*ilEjs~PqsIJ?hSr8r&+1wOR+xymRMsGc zAuQ-cNqQxTibFjvbfR-<(Ok1K4x3lISES$ z8ZxRIG!6pEzh+Z*eKHk98xLp*-|C}AB!pg_aj(wEi38dR>B)m8{1ifio<4)na}o2) z$E-A3nwtdXX-*^vOA&;gAT%L>WDgpt9i?UuntnkHX|)#P`ek3kkPD3>^kGLr3bTQv z7o5^0r25rxwS@KuFjdLeZ5W&;AurVVZ{9_5!+`2qv!L23cRzrIl8@67^T{lYSuf(> z5^i)HHJ;FrJU|dZ%;hPL>3US+JeZA;+E8};8;c;Wc>|_nl)*7F)64qkbD3#%?ScWF z=^YIz8<~d!@N@|^zt-#*V49Xm2tW+d5YocK3_U{j0xY6 zTw@@prBfrVPu-ZP*Oz=JtS)m&3jy{8kW?aRrs?2(ki2UOz+PvRKgjqaF;*jnb~5@n zBKd_8jKClbX8cD&bEplbrjT=Ve#lyomqLx?vXf>1Bybz{q>y)jrqwb+Ho#U5(&~{4 zeOhI%%rrs^2Vt~2u6e7c!Iu%^yN4|9=BB9&xDi@n%x0fO zze7S*X}@2No_DQhb>tv}?#1%Z!o#bW9tf$Sm{OvljIF~JYM>E}KoA;c0rUq5p&i5h zD>1$-G3H6ZdX8b;Acwna?z7&xFBwroU#b%9Av+9AEuj0O^i&PN0RXlHynGv~EQk-{ z^!XoMm&yUA>xhK_=#9EL0!`;6kdqFeb3hv%cx1K~_Xx7WAV6=(Nz?aIqm6|l_uK(G zV9Xbq=_P5s60;a>Y9+qO1_d}In2%^T`#^k*PvgK&*v#>D;B<`tY7LBHcoVbvU2Rb& zNCH=bOfKaR^oWDYZ$Ud9$U;GO7j*epR3WFsO#*OqG<~kUKM*@Z0{|zY=HdW)9mJME zg&64O19<{XdiudR1bP<}ZB_;{HL$-9{&fyWMfLh~SdXh0E>XU?_ zH6M*Q5)-qW>FZ2fdD38 zzyWyJOsBoux`@g4oIagz-(>y0=)>kQ#Z1Zf;VEw(Ee0tNPqXOdUXaFF^8U*J4=cLA zbRu*iT!_ZGV4#yvUvSUj_c6J@VW{Cdz>xsbWkIc5KfGvD5Il`5IFmB z@>0z@Gfpi{0*x~uS4p%?XR;b2tk%*$Mb0*d-QuG2 z;I|`D^*(^xG8p~7&Q=2%>Dzo7&;|kc5~y)^eDYp^Go8!9A{^u7WNfhux_S3SG;m}2 z{3pAYSnOFEzh}W|pOM&v_vaLEzA6-}7J(cf4$hZ70dX3|wDVDGt12(~f~pOR%71_( zkM=yj2u;Q<1&ws&Yrg^vB-cfwD4q_fQp`dN@FXGi0#NOx0W_PEefF-I9WNg=Kd{Xr zf2$zSR@0N?2aNj#J~RbYPY=e2Z1&njlKOEk%&USig=6mG+W6dQW(yQ20UL>cgw~Of z+>PrqD7wmR8)WvJD!fYesmdt#uRrKlf0qbB0KCu# z_!nT7jc%5_Ek7Kq&51kV;@?+)S!39Lbg(dktXuy4dsJ2W&o2v$j($5i*q?_(j!eE; zf9TStWW45R?bD*J$I89au)VTPnFW`kPG=u_ zWU-*?(y^WX)q@+8DY?ffdeQdo%Hj(EQ$pP%V<*%aPzU`QH_LWb?>|{xF=&ww0|(wd z)vTRSP3)U*SbZ*q4HK#l%}=O0%B~jn*IKjdX7ryYOX{cf*ZlSLw2)nY#qUbk`b+cJ zmuk%aw&xsN&sv*O?X&DZ*Oxz!tk`^HQ}xkp)mIA=fR^2LhkY+?t%9oKfddKGe_px1 z?eT%VHCwjnjIuPOEd4ecfN&b1-YiDAeo;>Xq}hT#O#r@Kse2z22V!|eAw`o5D<_Mq z>fWEby7l6rSgUHs>_f2w2QOV}*anwu536d^*$cLQ--|~o3oMTO zQ-gKQsM@*t#v7k=NsH^Pt23-ALE|vZy`*{P=F4B7UYr6nZ=DhNX8m2?eJy*fx_a_1 zAGPc_+yXq#tMc)0Cs&_)9G`F)x$)QI?e9Oj zbo=RyI(SAm`D!zf^}zUdIoZF>;`g=5csq-v9&Xb5Hv>_BDI1Sji=2XwQIUt9@%ynSCU{QU8(nse&{ z?;PEld;ZxIFjkuy*R}om-j^0nZzjt3CcQYnwdZ@ly$}2PzyIz(oHRVUX820d(d*VD zORPptx9{)TUsvqE;a1>F$L#Ku{E@}>!_WF39AZ~33K-lN)ct+y0mnip>m+PvHT zR<|T@aIejE%WFNg7O&>~>2eM_L}4wSnoJoDxO63N=tj%Xwx2_`2cF*D@|wJF_{{I& zu)xjM_nFlJ2t=|x>-)_2g^!uOtL(e})^YOByPcP7JYy4VHDTxS1vyFQ_HM~`*&NH;6x$jU zvu%2NZQ{`Es%txwo<0p4{>SFy_r#A+@~)f=ymZETVy*4HqIyPm?y|Ffv}; z|Kqgn&*e6`>yk!`4gmetKOAk_!;-#OXg;e5+!;%LKRNjO(YAL#Ti#8%{_t-bz+0E^ zUI-B`K0nr41j<1LnQXw|Ab_HkOEs~{?E_L_3KIxzVsVGU0J6Um0B}%|fy3;Dg5u~j zKYZ;mHKS3IL@AR6%w$zc2Km%7T9{>}8r@_~I^BpbmD5mL11Qbar&Nw)wk34 zCg+o#3WYDuFPyzJx}Z05Ioo_$&FX99<@>Vl&0fC0^JKB}$`uzXoRc>EA!lghMiCjc zg`1xojb&6WSH~{@c>6(BQB=f=!+VlTRvbZ{BBB@Xa5BhTSiK`6^VpFCC7H+X7Tn1^ zQFq*6W#id<5i3uYUoKhMaN*;fm8Y-$F&I01iNMM_({59mb@tAPzzw|{L|Jw2LDZZN z%e%hKiay`J`tGWWFQ6^%liQx=&slwGqrbAM zS`Es}vhV2nU!!>AAEgV!jpx$l!*tO&E$kmx#^1^AGc+~K1Wr9I*dk3odNT+`ya^u` z-h47U-qBi!Hw@(qsTOff7%thA>vUynj|%I;evZX^u|2}#RYH$dm{QSFA8+c&=0 z{7=`$m-&-=`nudWY65e=?uOj=dXl&99&@Zrc;BKnUD&sZ{QO*xXi8U3aMT>?*W~C$ zVdo#k-NHcg@eTyr zf4jdN0Kna!$I$M_D}_5n`jHV5KG%FM)@5m*6TLVxuwwoL-Juxm9J~2S6t>&_&+dPG zoiU+B4KG`Tqn@W;F%amuwqgZGgcNYo5PQ2bOTg6L7E**%&S=!&r>eBnOq%q5Ir8O* z`K5PB##@*O`jLPI{&o(k}(2Y)+YvdFllhAk@X zq>b|hm^99{!fym$=Wgqmaz|_jQi z8Ty3k9=uB#M%X59vkBFJoE#lFU94oMNlES;(3IT`{4*}bh1JVUGaF!rU5eZ_hihIL z7shJQftKEO=3B&VwgXbog%@lR#z)E2PedP4Fn#|(2sBN${etS6bG;pE)f(%8mVCOomwCA5hcOiLwx&4Or*`SO)+t>n{^n?~86NTJv( znxXzzv#>|y-ze8d6Tt@h298H=-GOZ#L+o!73Yoz*DeY*plTPX>J{2o{D0ksUc6SuY)yfy-P)qiCjIzX7sjS@i=#PqhDS*Hah|X= zr1F*d$xw_&)fV#GQca#nZ<@X9F6)ni@v4qinoLIyS87eSb3p5BXJ*oyTS?ckP|gda zAQ0EeR_PQ|g~H4O%@tvW#9aXtaMc)Q~9w!?Ipku+)fI zYAw!{lxp^0jLdxVd@==HX>n(w-h9=X%2S!GG6F5{0eGH$C>CEi}s0j^pd7_ z{s5ooqZ~90y2Mj^ZP=a+_uVDvG;h zfi}sPb*7pW>zkWVp9oGQ*fdjc7<`9P=Lj@Vph(D-1CYlUL_f?TSSXR5>4~rz^rpWA%F&^Zklb8D8yNiWR&x(+H5-2_$VtmTr6pG zvI5hlc5_IZN$?c!Gcs3PFfB}gZ#;n(C*fKNvpC`ULc}0KBmr)^d_M0q^g&v>Wnh@O z5szSU>t;;#JVW6jM+hFe6yufMAzSx0$^E1$#+y0Wnw<+XtjDS;YsA4WaXSmyaT2(V zr%juoRkFo)4_#u#Jl-gXyuq3=Y2ld{yz+wGA1{h4=7K| ze%}qDJ?rzkDCDTn;spS7c?+%0BTFMoPrPCl+zlon0e_1dSuwgnJJYsdl& zG-&N1_;qj%RT7xYP61$X3Nf4m8u4XC(|~@u6MCg~o_f?57Xp`yL*1l^DpFk08c37l z5VXWPUQ-Jv zq3hmRH#cPYe^1OS#GhNfMXlZv_Qat7%NCk4kK~y*m8b9$DSSo)LL?x4&kBGtEYn%%8 z2XK{;c8H{9sjzGVqk`}J;sy4rim_72l&c^LM=cUhSc#XGJ1}#_Ox#GLE3hg>$vINH zRLoeRz?Le?N{nnKfcFdOm5Sj}$eIzd;7#iC!yV;Eddnei{x}Tv?uVqtLCDhpNyU(@ zu|fbKM+J;i1w@=_2cX#`HIo3Q3nQj0Aa_%-0~t4@A9#!D8M@w#$IS-G7R95d(IS|FYU2q_kpO6^)B zX$CtN64=QB5(e>w5t*5gG_I0IfasMGFo2@8(9GMwIR;|Hpl(LUNyY#4*?B}6kG>e# zs{oT2#xjBF(>6;_ZuV3SVzzi?(m}|c1Q|;W8A~HDA^!ezq?ReA67hCE3mZQJ5kwuioPVVU|r8;LosE zERNag-?X*wlT&Bsj_a>?%%Hh1@p5L>@jRY*nsRsF12-|+`Zxb!G|1i$8e-gNVqi}?fU>GAa?ZxHAdKYOf*r!TLCP=0 z?|1T#(%e{Te;9<_m|y2yUgt72@n+!836h-xi(T+d`w!H5jf4DX^=9PwLM9b}spF@3 zMK~JQ0dm)0g=3j=xACt5=;w2$0Ty+}gWP~+H(CjJG1T)kX334h$!<&*II7!j^M)VC zs}qo;RdsdKukM@iX5Y28x?6wO-7VWIG~4(1@WZ_9-JjU~n*`-o?t{VJEKB&1#ltyX zQRB+@aw>On`}rQ~Dz??_@$20&i^o>IwhOJ>&siKXoq&!(e4b(-O;|T;!NINN2e(~~ z${D$H_JW;r4|!(Y93wWz`96vCcoM0Q@N=NOe6ccJcVO|-dYWMntjPikz8qM35Gc%G z=EvROwVl4aVTgHe#Ub3q+F&>*tjaoA`=;>*>&VU80MaXJ;>{eMk++ixUCILX{jMB2 zg&o)j)Y||1dZ#V!ct7TVqXJrNSpsU zYMu>M4H;{hzw1vZOz6f_ZGw3<=;BT6evD9{K4J=1MoR`xI8e}mxYo%qJxR#rr~qQE zNM?h)iqjX_E#~bz7W_3QcNMcp!PXQpPWQWd^dl<`j1^+oTL^PHX`_o6W5wuLE2fTy z16DG;`k{GC8EmPkr62JU!z+a6c}D0}5z|@4Jn|OWDrCkv*_^UaG9Gh@9O`#Pcoc|7 zFji1edGTO{4>-TIA4`0JZ4l~<6xd4R2v&-jD!b2Cu=erLw?WMRvv^{7h!M&%IC}WB z1U9w?#cFrFap>KGrjYb_E3H+9jT6)7h}&k$86@DG6EeYNR=zQRrJRwdK=T#IN*taF z=5I5xud3h}1GN)?ImLk8TY(%Q*#o6WpaGqt05t@bN+8Fq$QUtw27we4ReidZ8sgmX z7t9bNvx0{mBoIB1o~U9|lLZ8Mg&>fdaz;II?pz6T9-!Yb&Q%#)wwhFm(U)EUOIi05K$V~Z9GmX!CYj-4&gMBiXJQ;Wc)_bR~cE&1p8enmSABF z1?+A6ut)$GnAm4k=sqj$j*y;jz+^&ZK@V%GfvGT{cMM34n6}Nxnqpzjle4MDoK7*sb}38cyb*gWQXIk%u1?o^>I1gAp-$eeZ2+W+&?ej; zZ^iOGs2eu4a#N>cNVj%E_tV0gKh(c$F9E+Dq$`ZbLX(ZZ7(mb z0`t?)Z}K3SJ^ybNLfIxtMu%eooiNg;^H80Y)u>`@cQ=ON2Y}CwG5e|M>6g3GT`6y<6a6V1v+zH3@A_@dj<8r$H+KXN-}3 zP7KTYrRwyUrqiZfm+*8O&BgD3Xb2#4U@<9BcLtwZ+8896+f7q9QhXr`x$ zGuOhtNibhd;Py>m->^c-ra!Kd+aoI9#ZCH>@Hp7m^X_#!=a>m;klRRzwbxXfV@6Cy zpsP)^0O8=g)A#;7C#M~=GIy((`9-LSL?nihCrM^sy?KWzrAE$jnMLa|B8nLL3l(RS z{ObWZy;MkB%VVs{ioe@;&NXXULrP6sTu!t7coB?hhQ+!kkS zva@~p{lchB?rT9jc3U@~Qk0Kk4ADGh#kDafN5%j?9m_?dv_pmbg#*ly`M=$gK7?A74$f4_?)* zw?hI;yzIO=-zsjZwP)t~P0?d+eG5uy=3SR}PG9j}^ZWI11uhv-J-r!+E5+O~3Mm4==slKhe@3t~>Ao#(t zOMJw^MCwy2ObO1jsDU__dG*KJu>f~PJSkwe zE<8E#K>en~_gy2c1k0UVA9H){x{eEX@t>-$;H22_>vmDeVTAdRZurEQSM?_+j`|Ss zIvGCEbK=^U3u%+R!>*)V??NnTpF4c3R@+Y+H>5aXN>ac+YWpv(uXA5m6!*lGNn>e| zX_LqKO+7ch>ROZEL}tjTsTs41Bh#nN^XpH09x>xo>R{H27pF32{YD!honF^q&6e{A8mKcT&mPSY1%q(x;f_PBemEpi$hFk_j=lru9{ zJT7*AwD{G&Gc#9zxO*_$@y3kTGnPN2MO&AL@tYM|+r;RcV&}QdIi*8NqI1gv_BZEN z3~P^`y*}z?^X!dd=`neg>ru8IW;dv4rO~XUslegZx7tQXyWjB zJ5Rm%JAC)DYh6)$AA7pKb~wEM_g{Yh&^~I>`R^~cKRdj+r8B>>b=S&*!4r;OzZ5iG zqpx~*@_~)(`!g?`OpC4!yAr$fdep0yrP@LN5i0M=-q~~8ZtqX~IB)W?uEjmV`x8EO z2BbV#`q!c>qgOm!@oGknrS4Qxzajb6+rsvsslR643tal|$M&UF50zJ|-kx9e_Sls% ztKUuj@7KSq3xpblU*Hc2GkcYHysaf=&6E3Yub9EEAc2w z3QLfU;3rY$)Z`ibM9QxShBDO!0Zw#zMfV%Tn#s08O>|J_a--|z<_{09K1G77!9!stz&Z49Vp%4H~29xaJ=gSJNnT~ z;@%Eg=%PQ%VF5MHl|sbU&o`F1;$>I_2K_jTpg)xsFCL>ar>tm(JzQjlT z+B1Y@%!Qb$fZ65yck!-Q_`~d#auz^a=EzdF5tPQ9uQs}N$mbQfWD7W)09fYjRdj) zoLR#sBPOoLp@`}&r;LTrv|6HQ*Xt^Cc8AINfj`5oLB_m9K(4`fN1gfjN zt5xpXX~O6uvGWS%jGF!+JY=Vn)i%!7x@$<|;1=(*YGUSR`|(x);J$j2qOrD|_a}^5 z;$DbX?o}LmvTp85WrpW9-uLW6%;hd`-00+627ha)%jo&ZL4m~P(`oRq&E&+eO@6jw zyne(xhl#fPZX>Iff-&n~27R|rLko)=5?}D4_Em@W{h)!D3H51y!Y3}t!@)F1GCd>a z`bjX+J7=ni=IQMWb}U0|^yFP34`7BGFIDl|1uwJi|GvLy52qOTQ5mNXHbOnO2G6m* zRt?Md1m(}KkZFUV&h~}FH{WfYbIS0i@|O`%SSEoExMYQ_A2EKPBWFR<$*kv1POyh? z@xr?;b4Q$+mV5L1t(*+)UnhN?p~0dW9RmRLi%@v+hb|fUXZCiq`2gZmgYulnEimU* zlXe?!!&aH;`f;(;eFsIa5_Ex6pbP?KZ7_;8EXUleVVRGn+*Nbh??M(bK54js8YACC zL*Lf4rnoZVzADF5>emRLrU(C7+d1!D*=l)twQSZ+p2w{H31#%a3OTzrK7qg1vKKQOW8p=N9OJW3&>zbdoo3t`K&V zmOC|33Su5W_@Y7m;fveB$Clz^vW{>Ht^c%VT4awC&-I8izGdafO%0e>GBjY@!dud& z*Ee_OULRQ<4|;nZtUWkwH82=X)5+_z7^}O@o(p3RpZo)mh}Vc5qYf;-OF;SEC+3u!;k@8laoZ(K=La6N=pr*1IC)1tO%vIlZpOCLc1 z&wLVuH|xgQiPh=TSECaJ=J`iM)CJ$?8JsMqw$sVCD-m2dmbqM5=Fu6lzzZEtYO$Uw zzqzU5!5f#YD!jg_zdQT-gC@_cCr@@PJTvfT2upY)`0~3~OKx}~uyOghvUAW-YYuQw zotZjVMlVj%6Y$slp%*GoF1lzc9sRlI-7jY%Zr*ri22+}8bul)@KDJm99BU~y8`<(}{!zdE@4EIg<`yo$e(XHbs_OyI90JrivZS71WyNGu!dj>ZBdJXowITM zgAi0{d-4I{Lh6+IlF@p)i$Lu*fW(;2zzTri=x96$jRstmF#jK#)PitTpy}2~QK9s^ zo(lA+XW_7rs!>KqGn>OhFm4D&d=u2N>xr^k=W~MC^j_fVU@H{UoRq~r(T7{Ef{{oh z%%koexZfKN{7rC627(u8%TrU~p_HUpLnQT;=M=Uckr7lb1~s%koB~cZfG?kclY{H$ zWbYMAqC!;VPA9e<6+@}rIz?~e$yQ?i^A@{)UG@MaK7bH0EAoymu49~X4e?-l*jc6V zeHQnh*@PZ!kS(>P$WB%_2v;ECgF>w@uY~<~Fw|Vpk_$LRfTz&T&Qwp)mY3y{?81%m zy;sqiWP0`08?|g}FLAzq)K5TM9mTc*BbQGJo-iBS#*uieWJ)hxVWG*I>Apnb>05|{ zyu`;Ew7QCDaixyzA;;x$?gsi^-oah2>!{B$w+zfli%W`zl^g=aVyy(w)etA2!U^5z zg686YUc^p*LK@U^hIcTff;yFkpaY=SmGJ6i)l-lACfK!(VDtl8W_0DqanuQXEM)?A zYq(hK1yS4cv_D>S?HpbJD6-4jh~%5-5+RzDNyNytV-30vpA*HFwz3lkzRR_^P(7Xm z!oh~*WNQ$E)Q;`dj4=@M;h}Z|phT}uss}s8`z%M3_hu+X42B1e_W$W@%(-hny?0aJmV0TKLh96D$vPiUJbKrMs3U z+V%!B1)67B;cyLzK!~F1Z7{|S9-hP#mYk10%uz-ZHUSgw;34s_7Bc1U=EVORJCCP@ z9(t;ABZ`I1ytNJodzZA@2+uPbSiwQDjfDi)%DQzs{JVjD@pog#Cky}C0jz<=tI?jh z1VX5kqpJM+Z7`#uBwGyF^0QTW+6<%GCt6Lt-JkPpqNp_;gG-1ICcnf;6!SW+ghT8G zs-~GVT1l^KG1v$;JWamJR9yAUgjfPN;#r$~FWby!(_1P=_as|;}ffqBn}ybJZgT^ zSmKi@^Yf|IfD?02b(FH$UeRgIJucr1xB_5*7m24pSR*=#C>~$ag7kuWd99I=9cl;m zC*DRc41_-Qhn_clpibyLlKrt2&9!;Cbqm8bihDqTSYXH>) z5oK!An~S+(^@MI{K{S1=0e0;NHWp;66;NT*u>y3ciMB1MG__eP#Np8*I7Oztu(Z|A zq8(>Jl6a7dNX;KW+@j%_BF%{-aFhv}8BJY=V3}N-&O=IgYMwqjQUs3^B_c(d(fEVd zipE3Y;#A?Z$YAZ=(cKWv;yp*BRNA;`Ny-2+Q7{2SOFB10)5W2&B6y4(aU0}8lz^H} zsxvIQD{rpI#93n-;F$m(O~7^?O_M1p4-lh7vmzVdQ8F;j0KGl+D1dhmt50tWBN)mvGR zn*fa@U>;2|rd#VzMy%}W9%Vry1*q>pu{VIi^|V}NiG!jft{c8xMA#P1h_qmi;< z?Wv%fiZ!QM>VST&@mEdo2&=ah9a5r^HLyk*pw69Ye~Z>e26$hP;6IQMYG23o*&<9W zz$#)$G^ra-=F!Fqipv+!!zmn+SL!~9m8y*Q@|7j$S|eoLI=@VKtezgI*9mHf8-Hmd zth&j)5p)63nnD+L!vW$)qYda7E#a?!F$JSMMy*3My;Dcz|5W?y={zT7fkBfjM5oB1 zFaqU^H6smbUN0CLo#oc7g?oXBKm$!`7ZDuWK*Zqif)0pdp-mLgFklVW)92uDu7wt1 znK8Oq%V^eli?rzm^+=)45rb{%IkC#5W7GfCjOf+QtsS06Y~^Z0sgkW+D+Z{H(nC%3 zNJ?-P(lV45Wj`@lt{aAF#r?DdG1yNlrYKN~QxFS_GpQxwBF!veF)l}BLK=nG&KNYo z5?mb*iWkX>6PpoCrEw$S41n@GD0He9lx9dl+C_1w}&W zLUCx4;Ju_->();_fODZ@O|lp~S-wy@*stZ)Q2eiEB3b;%vv~C<#DRy{%8uINy37Xc zw1yJ5BACKH(=%bN623nZUAtC0wi$60YZhdp4w;CX0hzI%=Kp^d*CP8#btIAaeqB5c zxe-95R7v~$y?JW=&IVq0zJqGv2!%FAsO1Z_0iIVw6y>~*@zUP@#15oR1_`WvZU%@> z7R2LfcoIs{t`W@;U!*#-Ut^=!g<1+aC!$kGtxSO0F6vKE!m%BFHj{LZClZ(yRus`< zcyKmPr)((C@1>_IkhvDMsvlY~gBFexULAMRGqt|G>IIqF308C*L5$8UUO>K2B(?O0 zB;KG1c2~}c>g}fws)Pnz0{KgPQL(#aPP#&K9xdlxgyJoIzWtgEo_4CR_{KUqUk^Kq zU_@VnH)`K{LZb!{c|V#h*T#!9TroOAoFbId9aP%b280?1am+-WcWWhp=GdThp#WwD zBJHJK1+|l*WKM?qkBOs?n71y8`raw4Vr7=_O(oeF#A#5DltZbe-my3wOv3m5;cO9_ z7OkBjLx*)2&oGu`cNfoWMq~gTnW;?>(nfZ`kzyTQgjg?YY8Ij+I^cW-ZBn-`4MTD| zDEn9QYOY$`O`B8nMl36iG|fnj)+MEYPc^y;0~zMaYSEygWD2g0m!Z^_Wp8EA=}=ly zQE|HUB)~y#Ka>Jsg%X)K0Ae$Bz8%o28^uy1>?o(t>_E@np$)~+OoNWP>a@>ahPMGNo88QXidu@DBkqk)vB3Dg+VEo zTTmL4`E3@^jOr~x1}`F$#F|CFXxw^r?$%uY4)whuXE<1BtO8Q>N)s$vJEHi8J4AHc z#)<{DVgB*mpI_eDH|oHn>YBI`?zXvsH69~jHGkXG-`;1N)UE9&qN3aXJcsnvy8Gq3 z)}2LO)3*=ZJYpf={xx$$MoUk1bnz=>6F;-r;nSv<8+Ljp&8*BhE}C?5II(0y38mOpuvwDY!istf1e z^FFQgJ>$-)z78(%dzrC60xvfi?=DzZdvwmxgD#8b-EbM=b%(UO_v+rktRu+}%f6Kq z+vEh7tNz?8x&b%{o#IawWB&c|?ZdMh$9@|-_}_nnh`>l7{7j>o;ay|Yu*18JT3e~0 zPRDG|s4I5nq8!9S0=k2vO@nq zMG7SxjN`=$*@4xW-J|;~t#lB*P%E}=V@3&Zt-eb0f8Zj&aT7_theIpV4UgW7Yj zsk$|RDZ+1+`bIcxVv5i8?rG;YT#vBO|u zRV)*zTSaVlF|p@a6TxyTk5!irjZ-MpG@l-mi{F%LQ=@ltqnXZMW-?TSZ|dbsz5Rr3 zPrWt9+^hb_-0Rd%p7JG0k%_^r-mLT3?XJk(?vgIIqB!StHj?mf8lnFV@kp zJOH6Sg>5CSY0nok9HX$fHZ)9joE}hm$w)8{o5XZFzZ&Us8!VfgXE!{e$Ra@xG=932 zSgl`N5$H$u!!U10Zb@jYLEOp1fO*`seZK1PTy`F~W>aj1*9F?eQ&4Svjg;$t*VM}l z*&Y#xdDjRwUBNQ_NHeD?c6g>sLBDrGaAb{U3;W5PJrZ$;;W**M#z|(#owOwYonrDX zcAX3M>b;j4hz_*$`r>3(r||eyPyZAP;i>OdwE3@?@QK6qTFZ~W;h(8su_Phh_3QlMN_7M`}(bvG};W5Z;oLzjl_L=yBh zRxOtU+icDxpjI<1beFK5DUl${q#Ml7+rtWT(NZqpkiSzzCE+`~xcSO9x2NLLkv(3v zt3^T%6gI}8zeK;Jj&0v^-F1{GNjURn?1&n*gP*>{fmmFe=->^bHTQ)VB>Xx3auDjL;=z+|aDBE&16`1mkO39jBO(E5UnM52NFPhk8LUMW-$< zyT*% zok+`0=h+M)lDWYa-OmyIWp2S{n&hA#CZ7z4-pN^4-9gY!3mKzR%=R!ENP!v2YPf%1Rar6r!XEyXj(Gm1o9pwR3mFR=F8eI8o0!({l)s(V&e93t z1d}!Kq@0EY0kx|z*D2Bp{NC`({0TKD%lMT@Y;6ZYp~1T;AIrh*cIxb_m@kZ5f0e9qM4W=ia>Y2Gpz33k&Ol923SA z^B?i_;SNuz6!Ilh{{)HdKF&WbyBu~&QXW(&b+1vVS)aiE%xnd$e-7wQF*-)``~`F0 zbh4fsYVf|Y|KkG!e=nx2P{S-;M$mfJF@?IlZgif8ITz3;P(!fi45)uMiA+C{;Cw)# zj?hqBsYsY{%Oa7d^K`je09r1V`uYo#oENuZtE~j~iDU+161KepM6IMoBpbY`f2qW~ zD==iiyDn7LFNqq-V|uAzX7)g_S7DfaW>=0~fw-6{6;t<^Pi=M*8B*u+(!{hlhn4~0 zuti=bGpL=q`ptoZN*oz-=P9qWMjI%uWF)OCq*e1^er7+@QK3OTE;-2aj=hb@XA7J= z+LAbx*ybD~ytKfPb*n#<6YoPCKL_FLI#=BA3jn-<%fo5`)2T^mzdIU5c~<(cX0wnj zi1W@Qiy;)Cq`o8GSXW+Qv0^SAWZ)XHmL?Y>zx1~K)&3C{)&SRq!eC~6U2)tRA=kk! zjz31iv>`~6d@t?oJyQ)kho=-DJJ!u!Zj^E^~g$HFNcZ^HMV2F4PxPn0)@(5~?ax-BskLO8!L~!fO97rHoOue|tW5Wg&{mwS*+A_v z$W_o6Sm{d?3>$1?j*M2IkNjdw?S&3%Al$1lVGGP}f$U7cySdd>CVAMWv5S`o)=2DP zVW9zb?#6|gT55S}phX>|XdTvgGjNAg*a8LM#I-eXLGi6#*2z1);sjVL*FZ!TNWFf# zaOE0I2=Qe^-F(tlcHIj+;`R~l`t~@LI*_Lx^i-KOFCfQ4X#i$+iont4X?98wu4|(L zkThIPpYhnIx>e!c#wv^pk!jqita=|FXqDIrB!`{@_73S@YVkv~WZ3go-{;WK0*!A< z>rh_}=egOl0T22=0tn`A2DZIYk38v+bwtZ49>gJhjljMUFjVG{$!)$4lE?JaLcQUX^ayz2U zoAS35J@e#JBpdfJN%?xnuRaOQBN>;{xCJEAY!=6Be61wEi{;j<_DxgY|0$6?X{E_w zm;;CCk-@uWA{s~nBcy(@I9*0!d9A?||7QYT*>EsE#WM|JrL|E=AI6d7Vp10(*+l`_ zwD8>=)CpMJjaUMM<0vuCvjM{YfO?t z%hLtPZxggz&^XU-l8cS^G?~3cZGMyhmj?Y^gXgAg^|~YRc2N6vlSA`b{kn0=ja>Bw z{&F0$?zBpx7c_&Rmqj?IJN)f&%6iKBoFDvN@(T9G1o}h*_t8i7&%_Zs@|Q{_@i$$ zj5Y~4E3Iok&HL@gtKVQH@t%Rr)R&luNQE}i0{vI5p;vc=K5uoehV1er0-o739(Fcr z?CN1*b}y~a9NJ>`;24FOh?A81%kl=2Adjb?Yo&t9zc*^e60FAUz|5eLE!F4m{GN zb{4hfhw2=}h6 zT7ODt#C`Sawy>VJmuZF+X!f+Wd-)Rd=dGb>+QNx=Oktb9Me0qZQz8hlXB@9#BX2-G zBMtVGfhX|}F_PZCuv-L@6zVbjUp0$TEUV2Iwgh?gGI<@ix39*zhYWCN z74$*g!q&(}4ZqO|l~wp<$359b_)>mbBe8HZ`DGarDrudrRMX|HC`x_d`*;_r17vD! z4Q|)n$8AtUH6%SH!>;#%GDOquE%lFxgV&KZ1@PGmt=}&Ng=7~a{p-E(0Be2&j^pw3O8JE~frSSuI zXpZwW0YWKrbX#yXa&tfAOqsJi5*n-b1H7o8^`W0XOvh^&ELJ%2MG$pi;0s#am84S* z{&@qzR6!0MxTBzzc4;?clsI++$9ULJC2@>aGaYcOi%LZhz7CKhQO-ABaZfXI*5UW; zv~CVk#u{R1nj{c43sh=*9Cqx1R!@Wd(L7GP#!JwOQtb{I*p73C6_WRcnmv^2TNiHG zxwHx}3B!`-sF(f-PD9q2!|_F)&(%DfgyS_1*L{NLTh`=t0`V}a8v0Zj6qLgqVJQ$fk z5U1hSP<53yJHbgv0P2eHgGzjDl7_Ea^usTa-AF=!2n&diT+bfM@ja@EWP7L#vmAM$lnq<`KhvB%WYb-qmoPg243BA@a9l+M8X1T*5g5ope6+&WU2I+20fm z7?@ z@k2KL%Lp97NJigqsRTf(+HC+zk0X}`t#Anhga@HDU|ji4(? zyAJbj`Q+_8D}JPq`B5a|dn<7|(cvKhIKHxU(~p`Y>1Y7DuAwPJz?qr8B59;RdLs`o z>K`)lfL}a73yGD7ZKcJyU4t5HVyt*4$u7d-6ta8HC0K;t=z_e9Y-`=gJNq^}5wb~a zh%qoQh!j*h2}xT5FoZbG-E6mw2pC>9$LoZ&tO!-%q9gaLVX!f2l2BG5le63c>7)xjmWCk2^H(Sdi~e4ff0~A=hej5? zoh;oDP|dqUjZB!GYQCSInO;t=`oozFjSD98<|)I5m?FvOx5(ImSDsL=BLQ9Wn6p%n61#u@~S5?|n5 zU;1E;=qKp)qfIs<&EM{nj+UHWcH0ZLJbTrNP}~EtmvsRqR@UBGJ*0u~Ub8&knV3Hh zzq|YNtzkGstgI54sX2Z}Bgu_WgS<6su+YJtAOdb+@?;v`niaJ&{PK8wrX5u~H7}V> zKGKi5lW~6j^ndbiT)83JdTQ*yt7R%QRqiwO;+Kr`e`SGr8KdFGf7=y-PYZMLZ!$&m{+ z$UYyBOe&BPsKy-K(db$Q1^h5HEZ@~%L}|aT`1tbf?^_qmy7(K!aEh%_<+PRB1*Ess zES`O3qu01CoR#wi=fGo!rC$)-@-;K&IgD6$Hfu3r zvRAFi%+?!2F|^7ByHaX8nF7|Dr{bG0v}Ean+Zpx49Q;^x=>r_SxQ z9o`%*sl#mJ|KX}RI}UGXxgiqdY;@bBhiPtOjJVII+EGIrp4u3QHpEFZaU2s}@TOt^ z@})LVFHe*Nbi4=G>$aEwo)lTKiz6KQrj_+JpfnL)&vIBNRC5y&O?Y523lPHM>M27b zO9vyojn{9i<-Y;y4J_vQ1E^(Dz}D>To#BV;&+$TI?uN5Kzjx;K&})5d#zQ-PTuTmW zItK@N^`uN`Xx;fdE$Q|LfA-WnlaoU3-btN2Vd~wf(-z(pCx-ky7>=*_*%*D=w`*z! zGVV~y{aJor3eDKsAg6;lufj(R`_RPat6!Exq(9hObD1?PliB?C%-j8U%l2Ip`%D^I zaP8!^x(A#>k$Ikde7e{AF%?^V7(SZMQks!|nSY%PmtGUY@ZdeiHxuFy*Iyk%KnQy7iWQ1e9T)uKjYxh2X77^|0C<|k+bi! z=4TGu;Wz~z8u|-t_|5UpjHacI%Oy!iifdml81=s9`TV3m@)tDhpPCM$W=CfAj=g?& z=nR82H$$@G`|ZttZbzpYN8hx5ih7poBIw@36k;r5iAN}Yg=(Q$iCfpU5E1K!Zx}JZ zYUCp>H5Yqy#IdT>wWQa!8GESD_L4)>2J}n*Soz8*xzFz1#kew}@A>x!e;wQZ*RuP* z2Xnq={qyeZhe!W>Fxy@XdlhCs>d+lO#i)!^E2fOiJXuogMq$A|XWyeAwv70YfAY-X z54qxy2-&ARGj>pdA@I^V!#Y^-Y4pa)>iIK2D{De7%8m`YGu{2f(N`Xxek(S(`u3WtfjXWJ&-93J&OUj1cF}-Hn5=-2X?e|_x#DX70=B*1#A=K2o zk6-+@GtmZrbd>Of+_``D1BY?K52qa(3tDNu}S1mwA5A z9P-=0lMQoxd}kyU$?P@j@-B8U7fno+ZB-?fY{~Zda-k$;#mKO2AANfid{MF&ed*?Z zzq}d?xmr?Bf6`3)>Gr3><=W{}lgdwBx*hUr>$TS_KdwC=yd~yJ;?<=#Cvz^ZIbdOB zPMljZWdk}e)#Jd*larTeRN0*(ddOPD|ZN}OEHm>XMtM!MTO*sJDn;yU2K0CJa>gucs zUaOY%&GvGe);V#@iOr|_=7epZcKw$vCwD#^I^}$Nce0I>e2=4Y%HIcvTFxChzH8aL z{2aIGfBv!M^r=(t7B1dC{o(s9XU>0pw@B@l{lsQ#^X16*iz~NhzX;uW_WH8-OY7Wb zyq>uA+}%^}mmS|eRie~G*_|i$UE3<2eRW6D$^F+g zS*%?9%*AESnlrz!Jd&FlZtj&;4V`hd>EP`H^1b2Lw~5u26;nl3{sj+PuleMK{NZ(lOF!Mt@rP$#KX>Zw{mK zzT%Eo!>{Zi6I+@;KI^Con)OG^*`?SYd&lpdcz)Ha#>$4Q$A6sv{riXEqm6@p-Se%7 z{n+H;SvM}6|3_Qu>)UX?9*4A5u`Q2pT)a5=+*6tH_}B5h0TKfK?|@aq8PRLi@)AW) zbPnmtC&hxf4Npqkm-jv?6_$ve>V3B;pO%UCH#{v5KHdAYLfkHTwl3nH^4a?6mkrN0 z#D432wh^ZXJ~zZW&3|4g^FR1}Q_6_f&#UB#fiJ4l(&xX}oH_U4i!Is9U%%L@C<%O7 zlecC5%Wca22Vd3}oPPasyQ)3#)sDh@^Iz>OdU^2Gt~K9YzuHaEhxQtEP78YL^!|r> z_pBT7rgyI)ap>#%>huM#_tnfj^m_k}KiqesYsA~Wla|DwcTIQF^WUB7 zo_qM+>7M0p-<`3R1if#5z9s+t+1~w!-=FI{{r3Iu{p~>?TE5)N|8Rcb<>3z(etvuV z;UZuJ_mik|LBAObXzXw0MD_Kz@x}#zyyTEk@bR)>UgO7h_Z58~uLw(nKXv$SE%?+a zqFQEGgU|GRx+cC7{JAUQe!=JK(XSdm{}KDW@AD0u5%R?n@4WEKO7fg|4@6r6eY?Jw1pkncT(_ZNPD zSoG@1_eX2K55D{Um|%nsSar_73_Q^X936PNF6#ZjGsC#hzn@oU{POpUnt4b6ez{}C z`@dfqOGAJ3)^Gjg$LodzM}NF&JoEm?Tho=$fBKs4|MJhf=2u7mdEfH={XZW_#;~9L zmz)><{MZq2?B}Phs1H9sTgDCh_sgA(MgM;7o_FluZ#^qM{QKQnI_$rJ=UW&3_jm7s zWB>i=JM-bcfBLTs8~pj@{-VKu2VNZ;{O{*q0&*cDAqPSC*o=QS3K42vWvU;5+*M3& zig2IEgl~-J5zcF@^`cC(&8mK_kW+_^leO||`*os}I@as}q9vlgSiCL=$x!+Lpcoe3 z^@Ke?)5q?kQ}+ZC*?U|?!p_*@9O178m|xKR0P4iy`ud4)9Y@8>z@Ed?WqKG` zWs@%)#8P=3UMw#|POukW`6m(d`8g;NkuWxm1X*XtkC5yY76@e;`VtK5K2YhTnazvemupb+>k?2#FW3ZY&Uaxjql{-$2&-2RVD66g3Qf<6Mcvz&F zjqWL)6Pg2f>-O*?ANbh>tNFcl-jm*AJ5a81PrwWqnkz`yvLp%OMw9*?OK$71eTdW_&UDj4ItTV21I{>R>Xg*COWd)s4- zjPwLXLg)x1fPh#C76d^}=u$KYDj;f5lp<(Qs#p?wH=uOXpn%u|(nQptfC#9;im20| zOtDQ3Hn8#y=UjX3z1FwCz2AfHeXspq*Ydyv4*|pT-2eOcdosOGuGj7x>|XG>UXph* z#N6{iZ{X*K((5NT&F_0~W5?&SQu?VdH_xG4<)0htyiY}}>KnS(_PI%xcPcW}^Wo6s z(C6mX>!-HF_dWdW>*to!g;$so8Eo=MR1GBp9G`8Zw1lVTo)V|+d61}RRu#=t=Vch4 zi+KSdRGv;|T-ka^Y@3c_DgcKjr5F!L+s*ARGg48>_NSeoZXOu`c0dO#KadXnag1vr z$2LN2EAba4YjW=7aT3U7caFuIAYw5l#gG9J4&q;>ooxVYzRPg4u^J9rrJR^9A`tkU zIxj-W`Z6H?U7auBhhoPs&lcZHWxy7Fjm_&^bh7@;C z-+1R~pxhi_;yhSu6MOdY&VX+$<}u49&0N8nkg#`tK-0z8(@~_S#+I%3w-B0BFL=M% zYP34Trt!3I`;@5A(0jMz{o*RbL9Y)Rc0T-}HUV^Ffm7SU4%+i*-V1uvlwQf*Io8c0 z&C9}o4FP8dmtH&Y?Eo+Z3Q`U)RRZK>70#j()toG8x?*(0yQKXgd)nk!k)7us#R(6e zDSoc&ahbI(<<8Dw>#ZMqgZ=k$mN~GkFTL-Yp7i^nGb7LKJ~rQoy8hwd3!43Jy{Mm|L6gey+dP+#J4+T4f zlZA@y7-UN}#)Y9)?#`)f`0Xm>+tI*OU_%Q?D;4O&ev~U|$Y2`cVoH;SA~R5Av65lj zO7TPq0nK2WA50!5138WuFkvXk+YZX9}lGRrrDoy4&DG@ikrmY%G;L=M&k#H$t2P*!( zD`|?$HmhJ#2G&#M=gzzyn?zKtHwS!biqbs+(8LuiXo1xM-yxqgsA`uTh-J0u5a?B> zjjOgmp7=32E5PLD>=$e>kV~$B!9zCS3R_r}3Tg?~%^1$XB2U$}NxdUU6rW%%%wHaG zG!g+@SwN>hvY{jZ6fIX91~tu)X|3?ldh9)#fT+Ar>EIY#a@c^UmTZRsBj<%Ra>xQX zs2$9Z1B(?Jke^@QT_)DCGMTVZlfM_o$U_S>bfumLSAkT~&a$petF)Xy`4~-_>~)HD zT9Y1vH$%8Z+4yME!TZ~^J~8IwxhhgjWXVQoY_Zw%4fe6y*kjx0IJ>ZlmoJ(u_JyLI z9=%A$>@1pU!^M>XZ}WX*pUt^ln<(tPaI}$r7FhedfbN<~@JX7_aCtUds0Qp?N@UIs zk7KP2=6u|+FaX$^s%4VFs3B~c?FR536p_UHj}!qoQ3&4JugPbv*;LkQx0qQB7i-hV zE;GbFmGNj(sKRx1On3=SwLzY~!$}5CjV*OqiBKAuy8=p2X~oh@oDg2wZ}$Qhc5Pia zEqIsnk|%Ze&bZRzFU&G>{DbbY%e7925=*NR1I<1cD({t6XKmvVA~iY4L+7%asoc|F z%CAmy;+s1)U5jeD7U1r>aV!oyd$7;Et0E+8xsH?v4=1W|gB2?)XHF)T)t8lB(=9!4 ztx_E<&w3CL_XW^)bz!kwk~=FP@&1QP4tgpND5sU4>O4@IRk>$ch2?13>+p&{$U%fD(uUNZ} z{${L_Op<4*$DA>wmE0IaM2^fUXMXYrHGzs{(V=7pNNR;lg zuHNTUy?lY*6A=l2_|S?7-KniLrS<%^df30bfdwc9k>Ig~WIT(O8E6d7tPl*E%vOgO9Q5^v3|20(epla=L(i z(y;^vIC*>NGrxlJY#lv-zrU&`!>!^tdp{+9Pgch9nt@1M_wmezUq!jmy)bQT{j)bjXzb)Bb-&> zhzuZgQ|MQ{+H0vfUE|jZ=Ms2T$FnvKI1=OHHY@?=Sw@Xl zfn$2dH}GQ0UkbNA=hhC@uISXdk-g8lu>pu^*giRY+=i-STXA-G{aJ@T?M2_tI@&in zuWocrYILt?^f+(EJKq>J(75!x7GJ+9+`7qcwbsg{CiBhGryn-p?Pmn!EL{67uZ!8d zq)V`-xVh&ju%`JCp=au*=?Kxx@A;=S{(ViR456b3*hpG7uh%Z@L~~O;Kvqeb7=8j~ zi)%IzQ?Vv)v;O+3R-~#Wvl(100p$}d6}LA|y?ZWDv^=|b<5W#EtB+f}vXNaM7q4+` zzDm?;sy!|&lN_@5(&z?G5J3`Pa403`;0b)23ML>PN2q3gcbv;#B2JPvP}UaSQ2{86 z&zCc%ernTL3F=FTo&qXXU*n@U$2pUnGD%|QNH#L%Z9Ze6p2~9&NJfxKn&4v4`v!pP zVgxWg$NKJX3|n&)x3+`4wXLqO7tou|^fZU?jLP{N@X-sH@v@R-1saB^&lOJuz@8&? zFRfs?;Bt{k|8R;D)a56+2r|}zQ({r^&n6~CE2kOV<*C{``wCF!PBaB6@hI3VB~GcK zc*==<>&zomeu&6GL*%=UvCEUQ+X%UU2h*`@7~8psT&8&d#k&aLgQkcv2bY2(`-E-! zV>?Sr$Y)j|2+(b+0sf96*&Jpv2Z=@zS0Un@;JsvQ{SYXqk1=p{(0yi){LEn3`6Dkj zBf}_iUxD4JrD`V0U}q7uf&=B{G7rc|%cMskAH$R5v@#(sRft=I&Z(IO&Fo_=?x4dw zidc~>R#LVHLH<(KIzMO!pTB7b3wtD3)RU;ST5Gx{^*D!+=YcpV(+V9Lrve|bV9q@i zkt@tY*`!@EI9y2yRwQo~U{9ABOBp>!MCtAwI4cg-Q$?JksH| z31T6rWgyTwED6-A^WLWOaV0V=O#b$Tb%INGlNz0s;~hohP27A>KGA5h>jqIwPF0Y@ zIb>WtC90p1(hesIAir0$&v1x(e6=4mW~NMDR)QCa$bJIEMwa$IM8mffnYpv)K`v{i z3@%k&ox?pI3<8JR;9@D@sGvXvL|cwp=iN43@NI4Wsm)=!eJa9D89YsR)JoU`iD@rS zXeN6j=Y)p*M~JVNC)WR>KX&&lmz1OEshKv%<~n1x3>G2y4Xv=0i}*_kmk^Stkm8B` zeF1I)G<#&}A7`+had)raibLpH9)7rb387dBRF@>;#5Kf@W!fk#=TQ$9^q*3!gMDtH z0jE3$D0gMBoebXNfE2gD)qe13KY_&L&pYm2R@|LC3^#n-2@lY*uZInTHj_im;q3Mlk*<$xwhaOd#a%-MxSZQh z;7~JVSVbL9v=A`}1F@c$a{i^F;Kbj9kl|6BPf*KJ9`zQAl!<8fFTlgLNcT?AZ!En$ zlKCo+H);Na#RZAqA9QZVC|h6+rKYYX{$wWh*nEImJ^_m z8Q6b{`?Yk9+Xk?^jItSf3zZU^9&5fF01qA|&65KA<>Zrmvi3}@lZEKNBM25#KgIhz zD8xO_4-*HM~vB&IiK#? zp4SU5Ar=&c`IMv0ELEo8T}?oxpEz$d^N_{+@G-%odqCBo##VbIU4DF|+Z!)izW?Lt z97eDzMzwtGh!F!=^oo)Db0Oh@mix^plDL!JagrkYFcSD6@HLa>S~mIlJ(ZWuMc@Da z^|SB{aPte}BjfY^W8>Ci(#vK{(PBhuHetn^_)Lg1vPUv*YQ(IoN?WGoXco6V1Bv@w z-0``-Xl3~PPhSpCelZXHvLX6Q>Ei7~ap@hin0&LZRg1qK-gDeCNymzHKHBG7_N8y_ zPrh9|!vw*XW~DjPW1z0$}ST^XU_iu>a4Kc{_4@Svqx_p;->t+ zQK!cNkEY{yPx${M>OA@2Y}mh{&bbR&?f90{|Asnerk~q|qZ-F<3@1DpL zR~fn=;i>*aowNL}Ks=+>pQgU z(#0R&CO*9W6Ln4k1Pi4I)(TUKNoIBbLY@CYo&UQ~=S#dCL!CKOMF4btnd1BBt5%zT ze!0K*(ocXU1&Ho)EX*n*Vdq`6d*p-#s#sP6dIAIRD}69>-Gw0n zoLO?5Gaxp;17g+Eu%ROWE?Qa0FX=Z076^!QM?gjj_sSIa#9sU026pMW8ue*HC*0g% zIMc#W0Z$WXmo!kR&y!13RZBokw}d0XC0t4OCX3Klfs_Ha8* zm}1iUI5gRzD56v%ryI%~X%4oRU7S6@<#B+zw;ZQ$rz@_@j`i9vb2hu!abFx#g+8)# z_tH>_j2=}{c1WT16-e@d-|l)jDtS{A_$v)lgV7~QFmc-kNvRqjnbPxuXz_CJ>gjN8 z2h^FzNs%1+X`T0$Ru|p}i)fSXJ}73l(>Su#Vs})A4`375=wKyfFI&h}MnAJRKYG4k z{G1~1bj(rD$FBm%+Xk2Y7$P*~DGVrKv48b7~b|52~sEmwc(^}o?j+|vL1tfVh}0=2WH zSG}IkuK3a)w)AZI{r=}R=f6x|7v`U>9Q7Jq@bJrxgd1lMj`xo)`u63fNUc#y^nT%} z|5cH>wDGX^^%t)8Uk4=ljn(GfFFjU&y;XXnv3CCTmrIkr-j=F0)wy}UT3+$>PTkU` z<4Ky3;TtIu$hqOd5{cQg5rV4E$MIeaAbL$XjrT%E~XlgI}twg4EI zkVw-5APs@iinX-|&h8DM`zuGS?gL0>lw=FVRJm%>7O?2}@Q1;pGQznU9B1GA3>Fvb z-o1Q#be07FJr1H-h}<=2`4Xk72V}0uFtJAhDc-$v(7|jR;gzG*kw>~cqNh#h_$3&? zfpx$S;fwfb6alEAp#6@mClJZ`-BBZVuQ8nz`61+Gpdt$5D6sL?;1w;4OK3z=XwJZ1 z$Gu{$1O$YU^FZc0vu2{uvF!4J_loqzIT1$ZInK9>}fy5~v6RX^!uiW2Vdw+ND zRh)H=+o=p-H!-Op19UWB{;DMD{QV?dU?&6J8I|Gmc|Jn5Sgy$jjbkyDd)NN)-A!?B z_e+qs&dgaO@g@5`*x(-9o&-@R<8|k5PgCZ*RXb?GPCj=6jbl>}rlq<~BvTkECe|rx z{1j>r!;Z8^6tYWqAVqyRMV*mKu0tk#QBcsX#{qpp-h=zdVbF zdYHipPQm^ee%5IeMplSz)*g3Wac08w`Yd>(OQpY-)FSKfM7B&Pr@=O-(Lbj-A*Z!8 zrw!{e8O*sjkt5g1y=7U!3klRz5TPP-FONlq0<1ILp#d2Je9GAvHHY@SRi(;@! z=>2@614_~0uITmGFTj2o2w;gKVl#KGiGpy?9r2cs)?LLtxkDO7^U;iSt`JrT>CT^6 zf4H)U7RXEvqQ%|jpn&G`B}P>^J!C7+np`2r%@P5pY%nN{ca)Ou3dls2_gX1=Uu+zF zbfp78b3nIy)kj@I5uQ}y6rD%XrJHs{4o+eeK`0>%L`0&@<~ff1C&f zLl(z~8ZAI;2}@s&n<}BX7Ld1b5e<}P!7f-}hsZzEo*Wg%stT;pLR>V_9c!SK<96{Q z23&;>$|BwumYslQr!tr`A2C-l3giKlfqd8p!i&jQ|5>S2*EL0+>9M@5XoI}g5YQ4l@yK`s=Vc~NUai73-GgI!&HHRTS&#(5<36CyB|FH#w25s7Y`~)P5J>`J+n5FiP?LjCOtn|5v~?v_9@SvCHTZB7 z%Z3;p#7~Yg-b;fp*9hBHVsg=U@WEd9pUU;zjxb z+mJT*I(&46K6e{ckx@TTS7*CeGE{dZQ&-k;=&`?k<siK*Su*0hp|zF(p7gcd?dgBC zz~^Le!pWY%d=lftyuo98drn;%K2-vrerh>o!~=M~<8)ua>DRrdReERMFF5lt@XY5O zXC}(eeC^F?_s~oXb*#M#z#{3lA=ZyTO`s14dCKtinvU_BJ7Z$rJdk;h$$Z%$S-$<^ z3O^0c`m;RgUg;sj9D}vO{JnM!7lVbZav~NZz7k5GdBUI-IeQU2J9qM-ew-YaD?EE) zm{dF!nJzzjA^L!0lKzvGjqTzi?&llhE>F=w4|!GSEz@fv7o5&x060|)fF%2^ZWtC3@=&AE)`%}MD|-uWjqtwmCygT+%W6V6W!Ym-@CD4nk3Bm&DP#TQN;zmRKvWMs8o``3Ug zJ1%BK%iiegzWsLbz5S(+t1o>{x-?O7>FfDR-ydH3`Rx*5AjjleI7t3z_60(_HmOpc z<(pj-##CzWrgK2mHM!fcyqMIXbn9S6c6g5McGkIMFt(ba=rH`=u`1-0adiBH_{&R% z&pJp7EN9fHOONOZ%M!0tf4v3fph%1>+Rs|K?R0 z`H2nTY$P(wMN%fc5cV46ssQACw06Ub7MEkuLBnllhgnu>%gG?uvfh@^Y>H z0p#X+{rG`Ck__~iS+-YV$UQaXmKTBwC=LoTR7ElOMmQ%K*SYie0;Fmcc0UH*iXul< zgk({r(KgVL?}v3jZxHQs6B0wBR!5eS-47UIS17K}K(H$tx>3k&r0cMRyxr@*p%{ki zZ`hx54nIrXijs|$ME6|M;x1e-2Px^tRdQks4nyI5@U^*)?@qAp~~xY zcAtZ+i}i<%qiXxtnTW(|LJ38~RQZsDl-xQGTriaJEz?jV zbn{9Fgl-64Ob4t>mHJ$A(go0vUqZ7kT6^Dy?wq*CK(B78^W@%RCCkyAlO1A)xF7mHC z_$qPHC5zJ6n`UX9+6|n^f36e#`u&O57glQ>di(mY+MC>=7bkvdkn7$of=>qxy;%YN zQD*vwz4ISK7yo#4L+kHY&I*_j1Iz;4VB()xZrwj)Ie1~a1gJmU_1|K-+toI@hO9gP z36@(h*ELz&l@gKI+xU-I?q}{8lrs45SWftivN0_RwL15A15t#V-l=pcUeoc*K>Q2K z5qC(FY%v3|bk1fB%Wc0(#7Z6I!pj>lT_7|4>BQ2GF{^DO)F6G&Qpm?+k zxcoN*u_RFVVs}T*(+{r)&u)J5v$A!~>ql2|CXJR|bXXsHn;ua$??5;7;ZH2L_{7;% zy+B{$I?O=Cu$(_r4c8^b^N1Jal<8;MB~sB}2I8xaYk4*8(p;{g#N|%|(Ks&9J(UkJ z!|T(sO#U)MV{>7Ik5O9}IP{xbt{ zRnKLuwqFKf6)n{Lir$ba-L}6HnVE$yG3GYpUo5Rr(jd3roGoz*?2a>vKN{WQwLPT++L|{Qdqq zuNuo$*Lriv(xRt3rp^dm(ib@NzPPo&W>)+Ii?Oj#6t8>)ujPY8M2TuY{r&XmPzfP- z>vpINfDpx!6a#Kq_mH7eOtSZS9}%dwWYhd3Q*T6<@5{~ZN#;_lucnxac8-3(i-!yI zv;m&6S*Ce|GjCF&7S;ihe1JKhbZ2CR$e|miBt(nDPyT7iBsXw*YGWdp-&J+#y@a<& z1?8;2v#L>eh1x7cNU|44E_ih5#R+lTATg?=06Y@sWorvM^zc5sY5A&TcF-Mk@qzG@ zPr2FJ7)^1Q>1j`&-T#Cfvd_``E_SR$0>$|4r6qcRfc)-hSIvNi@5lc?3`AeEWbI7p z>G-DUYsbHe+dhB7@1dVuN2K~+rZmek7oJ>C?;Gs2iL1}gn-OCEZLk;9824W|xoJ*c zK>v=ovxoke5#~01=vMmY#%h;S;l6!CciUDr$)chn*GMmNan%Rjbc*^TL`|COR#a)@QHkf2wtUqKlJXpBw7+ zCzi9lQJ)_lB)4mqYGAz~Q)e)7^d3L7LIqLQDzJXfVKjD%yZwAA!zwb_2{$aKF6~Gc z&&{k3vgk3Op@@yr5$&-v<8gQ?sKdx4eCVtQH-bt`2Q^Gnxe>gt?h+zbikc^M%vdYR zoR!H#4<&T56BZDmDY{pm?5uHCw;s{1+|M>taHq`YZX|mNN+DhO5%p-4>dwNMYG&e# z+Yp2VlMJNqsXxCbJCQo?S^?nk6v-Viz&-@kn?mWr>o6?3XY$yQU@J`4xC5}|H@E~i z8QWU~xYh2*iKZNaS{Qow*jjgd?c>*Uq5SR=eDW+y#5cM10cvhLA7IKvc)t&%;frGy zL}TDYdINx$S;+MpM1;AbogJ7!&r%)4*?tEo=(<$}DIy3FoKWM+%}xy=(4!k5YYzz7 z5P=bn)ice>!H{>g0a~D+2)gPwXYN%uAY)bR@O=j}sM9=px6>z3-GciWxVYl9mK@aN z&)K$M5g>9Cod8RL&b<#7z(GMH%WCX+J@c^$Fq@!dB>&X5GnPuk>q1C=rwl)o#>W-m08mY4`NhC+yv*undF0hE6V0bL ziB(HcuJ+finZMmVMG@`n-%h%mbK@@Fx$@n!XRVH{Z)0Z)cfDC}^vze=yP^NxmbO_h zkG~CHCsFjlfECfGH%R}5$- z8`~}nT9IsK%QhX|Y1$e$U57o>J*n-!owWsf&IHs(^w0!S=IfwyNC0qkg+pobViJ3e zTin_rKVfCoC0_=85v2V)^IxQ7ra(D7SsYqWddhlp1(bJ{2fZmqFw%>~LqstO2m67)g{; z78kHd44jT=vy}|o8^^fdihTbFO_zerB9zGq8Re55o0+#bNCGz-SEa^KfDn&Pw~5+S zO}-h1Jd+_LPLvf&_fRt3g~)UcQaYH2XVY+1;Nn<&ZIE`x#w|!nC4%rqKIH!y%2Pqb zO7M0o(+wprlBtRP>4!Oc)wOUT?B%Z%5(&1Jddg|_D#&3TVbK^Bb_~VwLgP_FxJu)( z1PAlLQ(WqA6A|;?VJ5`JzDX4oO2T(r z#8O7;oy6`eNg&POP{FZaJw+}Fghp+VNDdA&7-CsPme$}VUVg9{;=+d)O+$9eiFRKA zRF03c(?~~=q_8P>xcjXHi(ntDjr)MP41aeTvZD;SF%NkVh9szBm ziwvweA(rcnnH->wj8$W9*f{|Ob~E=20S0%4mI!m{fc*%r)_iH>QBXI-VBuG$5(d># ztPmt^+gxv&2-pNB$TJZp3J!Higv5=KrY_5pF6y(Lc^i7Wvc_*?cLG!0bhN&Zf@}- zS*_(Kw%<9W^A)-y$3X=VQU1(p zXh=6w1dy39WORW>O1#W-T*ERU+pOTs!ZZ+6#0HcXZY%%_do@V*3A}n4&66oR>K4zc zzupSyEMe*D?^@JRKV>>|d1iUoU@nE<;1u0Ju4uSF+0>v@mDV4hjvt6s!Pq(tXmIPH zjvXQ9XUutL^^?xpK17_jDcqJNH71K3Jds67Ufk7{PE?i$m1X&`(cCe;_^Q1lsrmV^ z53wMA)A{Dehs|5QH4F7yw%NDDtZs=*YDsu#ZEY^vH+niz-(dE5;;z+a_B_;B^;Hxa z3|^Yy%$cU$w%8)OSfm8IwUgXx>Gvho+_9`g!~J=*%m=&?G4 zy4MC$D%?H|nfjnj9u1N`&JPWftVF)8eFpKt_zQgoZTjsuhqF7+8{{Uo_imnk#Wb#u zYH;9NdlTCYXK~>y+s)Bd2rK>WP1hgHiF-V`S^vrOlfzW~C#x?$%>k7;7aOa<)m;~x z>hXHSOHILXAE)cb%kZilmyTD#Zn8_?EA)7(#u0saWj(dmvYF7X>#U?;$D=rba_V;- z>_8WZ;j3-mQBeQu9+F@^$#&d7zZapdjFfYY6juhdn6t z>v7;0U2q=v3wgbB5yeAixaFpEtBr$(k*)nTlQoa1OpByWmx9g{x9vUJJB`OW`3|h* z?OlsXyS8?81-@a}@kG3^DXyVs@135Av7Ua7Yuawt z;@*h1({zg~ky!WyG!_-e0U$JD%MMNEr(P>jow-kx6(`Ceu;oZDQbRMWJK3v)Bn5(f zvUaB5Cs0cj+CXbLJ;$3mfw;v1-`b9w-1WJYtGVKSzh(VazwT`@-|TcV5;9*SHw~_< zH{j#ANV=lR1H6&B3bYDhYJa^k?$p@#-tM+v)a-9aAPR1CW(2yQ4hW_uS!`qR!SG(@ z3AL)Dy;u|@dY7qH{BpiRb|CL8^k+K z9iw_bM!Y0AH!0}X4C>0j{XZbwl*~)7w2G3D5CH{ds2OI_LZliW4Ol&KN9t7BxZwh; zU-Bb4TbZNBphF!+ks^5a5I#(>SfqmX*&#s^!fB2^J{&2Su3fc*m>5UA?OMKI64>t^ z3p1w(#vF|96{Ol?P2g372+Kh3-m^gQ)*?8@;Zip z?;S?a5#*dCV2lu?Bn(LzPjzVT(h-XvnkT%O{L_mqw;-4JBSlLnTY&>dI&f`#Vi}5+ zBEd_+HHCxB;1VJB@2gk(x$qwr^NO}{33o-L&8j=eW0ZMZ zGD-5{sVb6Czt!|BWGjd0)5xtNDD)bu3?Xe;RIH6al{b*nQ3na)+~%?;PT+4BzPveN zIGUk6jt?g5`HVt7Lm!{P=Qj+&JbW96=&pFNj9c0%cvi+Hi_LPa1SGcLK~ylL`~c17 zAXzt-WdJ~n$y+O=e6EBa!Xx-`@lZ7BNdVr4|6)nO3j${w9Gv~*F#=*gK7x0+XlTcS zJM9;DyA4PhHsSIXA^YV?qhk&O&teY|u2GBcmIxv|HC zZ*65me|&~IXFkeM#uKf9(>Cx)GdSr2t4si=&qNFqAVbnmu)Y)X`FGc7KCa^kR~Eyu zL;|S5t`JarYX0qeb%-_iJ+txMoGaRqAz}A&f7I$8On96nzKiE+KGAKw+Uc+_Qp>-R zwG)^a8n4kB$OY00(pOb3eb) zuA*+}7N$9_y*@OGC!hM0r+#*}g2OFjfAZ9-g{11cp2~3LuSEA2|3YvYu8H$YbNtkN z>&Uvl65YzbcM?zjNl9>nr2En_;GxP;@`y%ImYB zF%5nQZr7OP|Ibcz{}1P>!rUVUqwZH142|U{^dB)C@433@zvih+u6YDvJhi{tZ2q-t zOLx5AC#BU`xcvi9UD@`&NS0gk4?Oi+-`cP5OXRdoYd9hhAaww(C`nzfAtTuzowK%; zr$=ef_rY?4Sc4L%BM{MaP!d#70sfkBC~oMe=~3=nRCi{G5~L#}+)|BYE-QW6nyoY* zgic}?{p1n!FwtTY<#i(Bhq4fi&I&|yS_cXU>Vg#n5hRdCg_-ExeYFI@Ptf@+EYxO|ODhqx*;6ZJJ|?`#p?~xwyNoD{sDehZePR!;m+=aBjtJKh$m^ ziJwT(tJM=6)tl+=!%n$FCir$K3uTKRlG)24yGO(-Awa#%~&#Ea)<{w>{ z3VFM1UPWxU=i2-!Y-`^1>pJpPWjBiI_!>30A6a_!)3nZ{o$3!hRX*4=W97-b|1Qzp z7Xd~PeKV7tH#8+(OEnC*2TI5yS z(w;VJopy;vW+)EkgV!vSlh|{IyyCXmJ(JPGbIwt767)Yjx4o%*)OKSe)#Sa7!?Nd9 z&;v*3-rnnC`KPp_=gc>lakHHC1vJR+GS^TAfDUqC)6|0t25yOttgJFA)yXt%6ay(^jqaz|i*W?76Dty(6U}Tvf+&_Q8{!wNK6(9cm9jk!s z62q;4hdbFc)Tf>=dO+ZJxZdF)Q^UmPinK`V&M1)s&H27M4>Y7jiF9aO(@h*;D?VA& z7lXnZIdWr5M-qz$!!EkEzMH>eMwLBQ!;w)2zpTp%;4HHr$0tqD=QEfT8u&i45IoR48jDHu?03dB(d?cGHe@L|-e6-NH)aw3zK`|*Ie z>((wVYptf%j5&{C>jbrJ_hZG8juTrtY+YRiB13M)T{?Oiv}9yS^rZxx&zH$&YGIJr zbXW(fa%QX?m{@p+iz|1%=q`==b(#-IeCW|r!Wlw5!k z2qn84ToG&OCe%8j(hW;PMR<$05jbQSUxf3atMLc~k=Sk+hN~#h@D}_)lxHxsbQMy~ zK`LZ0wry+xM5lboV}8U0CnR$vB9jZTCm{l&=4^p9v>?xjgbF$N@&INMlA(G+q!35o zl2x@xQ8W3n1Ub(m8~Za|1pqh|ERaOJnzTWVg))3bH8#lOvbcCR8TiI8-D4OaAYyoo z+_@r7S&d|yFQ5u23W0&Ka!Z~Kaygoc15+bA@QPqy#S7%tK%fEyqPo4e3xL@I+}k*k zlRS&WARBUa6RM(c!5Jl^j0Ov&ze}QGo!lOT3;GY|6+* zK83F-*%B@xE!BynE4}x&FsNwwuphvb02`#~vJOyBvgLaZqA7qP{5AI81Mn&UbEO>R z;9kPoX_GHCNm3B`Vy6ROqCU7EtGz#_4P9HFGKx6(M_`S?2$HYP4(AG&h1N3S*;i31A=_VF36Z!syUzR9gFV1PUU#PWfJ*ttuBQ*9ia)!a+CKehTKw7d^smj}UtLfC!@HjT<7TjB zBR~)VQ!Ur0>N{Tn0yzRN3W~sn&LF-XtCX1y&onqZN2j{dnT5IMq-scwM28F{BJAS{ zAdsp?Tr&}hnkfMC+0fbvN3uINnLEr@pA_4&444HLt`3L!g>}wSPJPy*MC1Qu)8ha1 zW>CwyHea+pP*~9IP-~o125dAdEW6TBV z8AsyxnJ>7Nv#PZN7z~ng9mJ6MQ821^z};MH%qNqtG&L7hk?ANjPZUMjjPzL1WB}P& zNP^3b27W~m>#z|LE*w}$=Rk}6VIs`yq-_hsIwu9xC#)rE$y^DIE3HoCh>VY~PchYz z>O#ZG;45V59&b?lxK0PxK`M%Jl(II5rGdr~#rm|7!&5*@M`kc^&L)*aT7p}+K_rG> zd^ACAdlrvoWix4)$pKxjeOW#kF02rBf)XRF_LwmzlT*N&s@6TJDy(-8&sphOk({VTX z?2@Huz6UrdkFBE%Q>OHE%*eX#oVaB6+9WIhd=klAK-~uZrYFG96u)ztg-y$*8yOf; z-bLpjZJ(!Cm)Sbg2!}zc#)!I(tBRY;kq~#njGdoEdFI=4lWA8_$09P_ELh~MXhqGZ zdbklU=O6-OMkKe>a=#R$8`!K{-a1QLfdI5H9@SQpWM#Y_n7Ti$6F+tgbOj47{lH1O zQKyKY1&yWw^B{&ru&KxUOsN@6g6eq-_ch$I)KMOwvPilSG#P1&qR(1>j96zEeimR~ zBP4Qn)A`~wFJ%Tbs}64?y`Sa)KzAJEV7@RJA2wI}F7TR66O9(Z0jLQq)>`>}KR(hI z)t%tN-@lyEsr(AET|_qV3sdd(B9OgSADC>9K&f|t<&QG6^lx2;o%vL%a-JuhFWTl? zVWKM%ug7-J=iY&&2N9?M}o-Fo4cB0YIoqvV$Io8RakrJ!0Zx&P~}i zp89cDsYw0%0SNlrNHXgetmFWnaDQd8*Zd=_bV_eneom#5{V&L${|T)8=aGcsr}Yn+ z>{kz-LXQ7I{&Z9C6#XZ#(*I@k<-f_F8;VvM{jGj-w~^rAGTFcCC;!T1|5ZQv=SZTz z#VRE*;#d7-%ho~Ci{%H&3MN-sEHr+3s(MXB>7kU6 z{B*<}haKKRTICXKlp-KfUG*6N0C#Z1nneJ>cSJR$nNX0w2x_7cPg!kP;$ z+#y3iv|bAU2ItmV?0tz%2r#s1nmYgq^dgxsz^Geq9i8PJNdd|S4BQ=v%dbO-^5FQ+ z(5Qa9NeQWXU-s+BqxMgxiISCtk|XRe-pwq<9h$W2L}DFPi!zWM`0=pL^*Y!2y#x6> zJ|02o$K2gKZWWe)thVqz=CP{xR&m?M8cyCZuZ_;dV$O0@Unb(tWu(6BpKH~vOCqL@ z-M$!OLWHZh)3b+4w3O>nmX5o5*;g%=64IQ7VujmED9Aba_%7>IZU`5N=&7m|#OC0g z&xzE+2oN!EY2ff-Uu_N-{jG5%=;h&?7v82F-u=I;eo{_9EzIzI^i*%Wwa5FH{Q2*h z?DGyZ|5YWkuo<`8?gr={GpBrDDl~%+a@T5~ANqX@(EhND$OUjtgWLcF@Lzav)p<<* zTx*{qs@oKQX{W9*UpGmkp_0hjQUxb9Oh*VcgK+O$jqlJB0AN`!c^)bt_0tT zmRAmrtnq~a*kC(q!@q(a1cvjlzLv!uqP=&OL?@En)&7cPE}uv81A-wsAX+*gBZ&P4 zlcdPtdhU(_criAcrCRhDGwU<;S#0(mAviq_#UIM9W?1TU6|Rxev{Oq>LxrFQc8`~a z-o_gt?iGtKr!PJ)(#=5JgEkvyr3|hwz0pT74iY}iiX24sIpQ^)sATyvr~v1a%taZ= zDYnbeu&GO?`}5Pl!S$N6lz5l@OA1X4ct?MqLgKttGE6(yqyG@ih+~;@hbN65T!}C_9va;5y^?u*)r`-#T$@Dd#QAaw~ z%THR%&lJ(>=2&wXT*5Owf2jqt_7#oHVVGd#onh4N4(GcG%9`N+H zE?h69^BG#`&V51>S!iC*IIQN_vxY@BeBa6tD@@i#H^^jN^_bJJgr3#OI$F;2tk1$s3E32>h%+V1Yxm_`wr*B0x1 zK1-Ri>-qLPTN(uVJHV!i+4E|xH^XN&5dh%b)nNd=y*OTv6X8u5Nif11=?rjt6CxL4 zq3SRAO6nOd>no#0x6h(IR~)4$dHZA|Mn?!OL^l330<$@A69d-jB?b#Q zDS^SjCA1q|TYSK+*tZ*t1~)IVUu0Vi&C@_JT@VCrwb4K?c6-mX!(UG$Y+@~k1_&Dl zuoMuxX9+xwK%&r+ws&pX`4ke=!<)2*r;!qTA!ytWe43^o!Y2x!KqjAIk5e#Su+GyD z@X6SS0n?dJR}qo$No}i&v|aGq)N3k=X|#8PC!EHG17J))Xfg|4MN9+Mj6!G9p?xD^ z6xKRQA{2*gptdC?CkE~85*!|vlasqZQx^EcNT%&us*7eVBN1FI181XBk7m$(-; zos3tJ1c}zo!CEOwnR^CyIM`hV?sfMLkcHA`lL%ZGmV+dD$l#!{0g{J*%g4HgL)GBW z0bDFUmoS{%oxDc38b=Z2Kz;=bVHmMLL{Kj#X#a{3Hq<)}`3sMCU zm2-FE3lodl3aA5N&&z4+Mj`L{Vb;|<-XDY?b%f3tSl~LG@T@f-=>YP}Xs>Q=oL0DN zXSLSkv2cf9q0NaM#@+PiS%sDd3dwuu7TpQ)EkdYG#>_3k$0Yxf3V&+Zad#AAc^6MT zU6}7$q@K>5P2ndy=O$rU#cn~x?#qhvgvCp|kXI!pK@RXkDg60;3Ge5U*;c%H-qB-2 zaO9rS&+LTw+ocYFKjNzb_kz2%XFvXN#3u|iB$WvGBC*{rY$M74QT0FAxj#k-fFWJR zNECtuygOeZYg5sz@S&~df3tI+qb-0~>?LU=iiq)eKzkIliv`c^00r6`2GVscL$WnU_Dz|x=?H9p6m}B9c(mGp$Jy%F|d4F1DHiLQ>QmBKg`=? zB|CI%!=+B@bj#{2u*9~!i|DnwT5i2(aI%9=yhFLv%&SHvBj|nZ!tc_PF6pg^(V+k>W4jP@lB^m?_funwUhzx-O%BSz0`|fu;cgjA9Y%V{xPz96Bo6LFrl&wxtp|2A;0!tymB zMi}c$EP(wE{s9+AuP?*1^F9B!j$8k{6y^W1fpba>HZx`ch&4j$%UAC6mhDZw(7B`{ zui~ zZAnobz%Gk@8LdC#Md=>wy~(elmMql51>*i1Oj+7sD*6wCNtBIGs-cZk|KmAPAr|kJ zJXCZI;S~Q#s;Kaqrq5sqmV#CIq>fRN`=7xy?@@S-FQu}8mbi4F{tx@M*>dRIH&R7Y zOeO#7@1zP*;)8!y*Yy8bF#Q9m0$$kpWDyn^ds3Ocyi-48Qvpvyd*DzMv~S)9Xf&aa z4qURU%wUbi#nO3dgfve8o8{-7K#@$m5$Hd`JdF*MZZ?YY2Y_$+NAmpU+{TmNNfi_K z8e&>*1|J5}Xmx1QDht)&n3<=4c3jY;t0h$={WWGXfSEuw`03x<@wzB3(sjsS4F5YZ z6X+c4TmDzN$uJ6f{Au~;u?G5sZW8ii{2BM}bd%uw-6hWkzgzw_DwfYZ`i*XK_xfR; z@_$dV$_(gI47ik_A2N$}A;saV|DZCnw5fwUC2_R8%8 zAOtV~oFP_YkE5|=Ph%I(=|2c)&5j!OXIVCu?H0S4ArOXzAazAGDfOTgRbl{EnB95y zFS^NJ=_Z%(E)Y&D4ftZs>HzV!u(pd^l?V{*D|upY!eEFNN;xP#e7~0*w`?pOhcJ(J&fMzSAJrbANsi$i8rtx$oiY)xN-`2@eiD`Qf?) z^0s^SvCRDy$A0{Ja5(tbR-kw4_Vc^@*X%#{(-ke^!q#v8wxG+mVbc42+67(w|c zP2HnXAoVf&hVqT{s{vA0i+S(vUaEnOo$CR2&OlHy5BiL`- zBK!nllLWM5!QoViRvIrvq7wl^K>#Gn^+xG^t(Ul`8E4Db-o6*kSIT9>)1CD%0@;K31_m?X`_nL55>f zR!v4LxJgxs<+D4kYK{CyJL-L=8UJO&Uj(!QD7X~_@Z*SBz;!a)#l<9c#|KvRFI^ja zB=*=)(}vzxn}5(KGD0n#x6!0~qqZfl|RX|Ad73Z++wMPOanrAFW#df6v=mONjpp3A5_E zC&YhSDph+j5d;8%1R@X{P)N&o08S#n1u_K*+Skxo zusZ1Qd9N3j&yMq%JCm0sg*MiU_zp{*uBNYgkg?5T^P6#%ToA}GBwnmr+r4sc-j#;* zzlHrF+8-|}AmAj{uBioBYS#>9{r#F%y)~69UtAD;Uvq9_Q-*d;jSgaH7Q#lV5icL- z?$ozIfB#c*skl1Yvib5&Ht@#38NKWaFH?Hdc>vQ~`KD3`X zA*DF?kMwcu?rCn$wm0W~Pal$Xo3?D;n)Q47Sj?tE0Ay$gmKoA$h2PRgZFYcRDFlLh z%J$W1(}%K|U2zB`VW`-n$F=FBZ9NBP#nWb+b5-sCN+17eq0@g+`sn)aojx`Mn_}eI zNk%QOj$5R=s>h zW6L*Qquu?y$=^pRoC{Sx|0!PMhyPoFjsJTMpMzWlmgiJ&@o1{M2`3-#1?4 zY5bp90Bz-bs;&Jkz_;4(@t4Y(K}!yT&=b<{wSQ^P$_E)o<2ZhXXXOI!2$-+_)=zxP z74uB~TuEEx|FY6wihPD^au8W`;E$EMzORIhg#Y7}wEf6qAQOBDody8hIG>^IY^qWl zkN(dtB;)9t%>O!s-tQIpkE~U`XKzL2zV8iv z?YFGeaH#Qc(~)ltz4Ca=i9fT}w~E|;v7_@+m->&a^?$gbAOAO)+5hXM$N$N!74pwu zX74yDP>5+`DnZ%?B2Ox!7CZw~10n z=1dBJ3bl5Lzf*tz`4=p^PKmJ&x!)nbJ-p)ILt)^}&F1s?-(|y0^gw@S`AjFn^7#6E0PV^xT=D&Rrqg-Ya;*x*rud`J6gH|`O zu6a>YfytAf%Z=ZS+*EVPT)0DrO~FqxN6b60%UbpD0suLrSQoid(_@5|@S-j>Z8fJasjVQwXL^P@3 zZA(0egL$YQwsrS>vC^HGu;pvYpVVJ}kQqW#vTb@Tw-N0T{>~BM> zbB2l+t?RvEz8E*!5S474T?%J2{-pk9d%9cYuZ0dZ)0cVqjn&xr&*2%FSy0iszzy%M zC`MDupDkMC>p5Z*t4DsX660MzYxZp*8x9x$N&Q_w+buu7MFu;95W>_Bvx^Aw-iv3T>3MgtHd|+;?Ua(M-%U6S zMphyZ95#3GRljc&y-1{8>`8hSu=zfS#j9zy2auEZCi9LD2F?R86V!9^=Ib>;omP4N zky5A2J=Qs-Cq35)Qy$N>@&{*?E?9l05Qr3TXBm##%WRQU zRa)zBE`1G%&HG5Oo?fTB$y zN{KTJx%c{Ctef!%^>R*E=K_yPUL!lblJcE5GR2BY$|5$h#c$#O+LRQ!mTS4S zqyH7+GsipSp7P!R2-cBGj?~QKrsdw-&GzRJwVN98x3>LG{oPrpfId{uK6FX<2n81) znDmNZMs0)78=i1DAudtR;Mvx%Ot{gMq=qDr#l6j&pDXSRoOiMu%?Kj=cEqLb&3EeW z)}N;H`r*j_HVYf<7*OZ$77DOfvh99>#^KD)b7sJ?*J%vAH4AInG+$wIUr5e-Sl+5| zmwEeYz}>RWFUj_laqJW@(Nh0`LPpPxnyB{Wer~W=# z4cJqr$0wy+^5GKq;)GhY`I;XMi1=(6a`3xqoq@gY9~xbG)m1&^E(K|u%&yEf)S8;h zvYcn0f|nh7`~8gQ(`ziIyVe*ZnFvU*@=rS=F(f)P$xnGOmDXWMVQT3!D%Fm2Z>&gM z0I$nC7xw*(=o8+yBuu-}Tx&x`GQm=70kZ9d?ku{&?Dt7PFx&z6=+}Fm>b|@>_L6R= zFfn!K4ObDjoukY!+u?St)RZR@2D)>o9}R`GHvd%hS&P3|r5rzhIGLUPb=}P%ON6AW zw#M||Ip}ZF)1U3e!CW!V-AVVNQ`qv=y5D72k*8QpDB?xKciENOfhxP-Wmow`6r_^# z_l>$=FT3-;-plz}$l~XW={FoAymcCRIeAx!Z`13!rB;#OsK1Q^4aULC!}k3n_4kNu zUYFT->hG7r2*-i{a_aBR`c>DiXU>a8OJA-DhY#W&A{r8z$`tH~p+^xbNes zVx0mc8@XqNebOpwUdoo`$M4c|bxE6Eud|qQ^{G!|BF67o@Na8N$r-1hXi`3A@|e-h zB@0et(vVG$j?b4$Cdn|%RtMHl61GF1E^IV9>Xf%4{>k#))_pUMu6yB_aNMKrjBcyT z`hht8*LEXkHRtm(x_d)D>W}4i+e!h`FI-@L(&#XorXsUv;TQ75Wz`R1DR zD5HU6(rIq@jY4i&^_U&YKVWJ=pzr|$x(aAraq^g?#Ty$vC1+wyQVcrOtu|81OuY+1 zECT6snR%zo7ht{CoO^oNWy3`4#o$J4pXW!HouDSc1k*X*g%~SmowApay!eF%r2L{O z{_$@_tq@WA4p_U#0N}^;#)rGdO_B@qd!DrtmPO3JbPsk~9K%4%fZ3zxgiB6NyBNMr zeVH!K)nn0Z2d{~o=2i$a1e0+M;9oNyQ*v*b_|EnkD{-7;6 zv7kQ1Rfug_8?I}uKFwTQG>6h`JZsl7re|Hw7?=YbA@m zzFcS|a++^-boHi0h7BMpdC%A6rpw!vMC@$p;z_&8@q~Pl~_= z&TP=2TvbP(lE09d_!uVr(m2IhirD(&1IDgeKV->#eCi%{yhS}ygu*kmzkY~1ana7u z6*faxnrzQ#XPi^ynq#*z9>z;8_l`-^i6eSKjo9rY0_D1+&%RiyDecuhG3g=+u}Oru zTgmA3dB)`o?zn#4TiL1JB!eB%L<&Aa#)H|0CJgRG;df-`y78b|42d>Nip$a9;oFI6;VF zqiDX-dgU1T%!UkTcst}uIgDSFVNZ$d=Tuz7tQ#X-?1cO6;T&Yet*Nk8$+lJQa2#(9 zrkNUf3d7gh?g%_L+l#bbX-P825;?$4T5B+uf+^H_E*I?)I9exRbWy-uu=+5ZUdo$F z?w`S9fYdR7#f6y+SXqq4L}J&Wi;b&20*~X@3kaiJQ7m>4S46sZYO#GusK zQ<4pzIY8`7LmiAO|x*DpgTBY$*5>ROL8Ajby^zUf%%BB??!qhAiC8EUF_$z9bb56sEOh!4qd(wwA+h^;siSF~~G z;R;1c>1$!80_zTMr-0Cfxb+N9-rSNuvVhP52sPF_A|5g zDpmOX49YSGSf+q4!n6x^l&IPqr3#8cDs=p|O=eI|Gbk+!vJNm|_kPmmp<<>)A%bJ>z7&Wk+0<8)zxXIKUO8(i8SQXr^_*>{Xl>meCtmf z83o?hQJ!rC&$wfC`*Wu8rZ(8aPD~1EG90T(ul597Kfx6&IFgIcAKP$r+ySP*0R8}e zmIG`y8@c8{+RCKlGvPovu^3I!njI4r4zxzRy`tEZ&y=E2Eeju|Aw97t#33M!@3}yX z*~j0nM2HJz#PLlom}Jk1xzL<;T)<>kWq;0O4Q|#ZBKM33HBl6K3e_GZ*EUEKSj5u^ zCQbpKVI9g?f_Ykli=GcI5mTNlz#wD$m2myyFGqDaMpyyX=`;{KEuO@p(~@{`3fwXu z7`JqFLk5sI8FZWD@yiq6SlOy@&s0EbR)~-&sO@AgkirL>h74kPfk`QBb2$jYk}0ho zSLQBtISG_~WEu0pD<$A;0e1P)4Nw|Dj}rVkZU!0xsj&*&(;|SN*^W^yk3~WDS@5Z6 zOXDRFW`Tx`6_zT{TuY6(1paEaYl|&y%8KHv@BCoYs_7)SR+DmM0SJ4nfU1{8)Z%n` zKxkJBe_qIY;<2^AgbW#3=>mYb8K8^-v=d1Y0ZF=YT&z5S9oIVN@Um#*O{d*~omvqg zAH13c!b>-h`Wg0nfS66I^>|ubF0jM|xEeJbV$7Wp72z0yh(cO02uOzkFA!K%4KH#v z15|)13b?|U4|O59wCqK~6~;VJQ~|;ot-b#GrOejN6DEGBJ7d@%%AVltvv%q#XbzwC|wuQ<;>B1GpsISiC4M!ax9=Np3AGYxS=`{~k&zd=dI#I(#P%oEQc62Qc6y zW9r(o*pjnFTi~@Hnaz%X{XIr{@!7L4LNA0U2&{#od;o?qmW%|V{G9Xcb61jESC|F7 zw>{^Io`?I+Hz&=uwZA|z1`-o5Jk1w;#T{gCxe&`w{>c$E%3SvI+0I{DoWiQ-E8j9p zE_Eiqj9S=d$fugw&A+sgI{)e0&Uds+(_JsEitmcz*a!I<@+-SCT)Wa&cA*6^q(s}9 zOBU@PW+;r*dE@6|#05SJ+DxL;_Eq?)^W(eAGP}zwyZ4^!uDsV>^|f1W-c#e-Qy1S; zpV`w;+0%Hgr|DkL(XTxU^IoNIZ%cgd$;{r?%HA{Qde7bKz3{cyo71iG?VD!X-SSvH zBe5^My07D2Uxm=1|DZu%sNwZcI3|s&#n8_!3!m+2@FM-xp$roe*cqNJh*)wZ0&lRY z2$oF8iq3-fU-tb_sos3rFw;EXTEpdp+9gwI7yNCmpwr>#PZ%H36}RRq5Yqa30*1J8 z`MajxjM=gfKZc?3GUS>h%ZIE+W=R9#qXd0= zAPg=69mf}2>HXT9_5w`%W!f$`ngM@@5*>J(xX`UFbb#Vax(!fympunAOiDORaJ}|4 z8#8Cj&v}QwtHq)|4s5CbLN;7KMKzvNc;=T$G}nHk3x3yd=y_co16jLKkMHU!;KT?| zx{M3IVV9B^V63YA_VorOiV5MJFrs1n8)zlKR&yEns<}n@7MSU`^af07B;34Eup*d) ziD+ZySkO%QK!`hZ*%~`DKnX5gD>=g1nNB`>v>*ZpuJdu8s2%_dG2g|9FZoK6YbVw7NSrwfQ{$vJM%NmU%*<(`_ZIuzug#NC5l-y zEpR4KW$fyi;!GPl1@Cj`JAi2;OxTE-8x0l64pSqs8=dbTS-Gtu2gEI} zvu%T>$q7meeQcc#?#tHI-XSoLmC)~`05Y(yq>Zf*MFXap=}QIGl($oOeCAxuLG{~x z2p?Fbxo0~--R1)yWKbLs9Urtpw|Spn_EsGi^6JxYUN>CMfYvi%)91m|fjwLX%<85c z0y3h2JgEwQ*%`JCh6@;!Mmg~i3-{X$_z*%!)KIN2q&SYVurFr7&d=b3ui!4tb_Y4N zz_d2q?(xpXO-ccv&xe+=;1U_AE34J95<`W})IyR&KQtSyYhvj5_X95WtWKXx7IgU*C>f$#xeq7CTw6!DKuT7ybZal6dw-0>~4NG zYqQQ}!QI%^?ok&WZF&xEoj^;HVFFfT7T8P^@0JrjQ^cEy?Ow3&8F)93eD%Txr3#3> z;1QWYF4(>9;l7cy=NSqGO!A?IuwF}JX{1*2`&00eG4ipoe9T+o-JoG*f3A{MlCv%8 z-WY{`oT|TxcddQvefxvDAh!OQ32|-BzR<*$NOU+lR^>w8W&mYo_8~n3NKGcpA$S>Gx<65LYd3Te*Q? zO9qRMP>${({mjJ9@`9VnALNX}4231OO=N+PVznT^F1UBp319Z%1+UnwP(I$iL82fL zq90$?fKV{}sS28|sNM+c-sfvKg$MJy%U5l4zy!lyDk^p0M<}g9mqtC*PpUw_Y;K1= zBVfNN@S0mcz9)e7$kreQs$@}xmha*M!CnErZjlo5dk;lo%p)Ef{|x*3lm`fQar$rI zhVQJBQ9mwY_bIS}#pfA8HR)LJZ&N;M;AME_g)E=*?*gh=Z5ZNM-X6 z6&*&L=;&DZ(Xe7;b7C5=w8WOMOATc!s+|%D)x+McH6xLEO}aa+^WJMXlB?n7xxzun zj#c_ZeMT%Z3y$%CmsfL-%C)ma&CpYU`)Om8|ABtO(GUIx> z?3JH`VUV=2>`7!0rei4jZ90qUl2>dAzMC}k)6)KG&Mv!zJMWhb9*R1)SG97v0V-x94GygK3~^m+DCTYzMxNcF;E7*4QXS(yF`a`i0Rj^V6VZY<5AS|5jH24Ugy zQ60D7ht|zv>uPR6C7dKA#cc3lGGlITDs*23PZrfs<{!c{C7Fu@>-tNO#_R>~oujr5 z$w7|92{wv4?5W`PqF_nCJk?mj^-Dc{N+HWKb{b)Gsm;2|>xVe-on+IP3)QJRD0A+O zuZj$uu>O4{*QPXmPO#&N_j5ugWoa1x!SCdCKDuYL!vREgU~b(!uhdd;pllK@!M&Vo zJOj={s4;`fh*`G&Ge+SU4SSg3scTIEV`>VsX2-C+h=t;rNGd*N9!JfP=q?+ziTNly zK#R{T#fj+pC6mg$;aZ2t%N?~aN}YfHoNeESt2*8e`vq152ZGs??XY> zly)EPQI9$}DJq7ixT$GQZu{J@FkR!2HJ9KRT1Gqno?0XZ5Mxs|uW+2xTPrI`AzXlq z3at-wTx^MXT(S){W?`H7b|G_fQ(XcaJIA=?Z0X}y?o)4US##l+kH4%@VN5aw?UA*9 zWw_*Sevx>sANOKWM*MMdo^HnE-7ejV6?4hklWI`c2%TEC(ZP|O%jD-aE z>PrvBc2XfGuW~(qZnaEPXK2+>8jvFSQi3Zkasv9JVyu0+n1p1(VQC0ap|?EG9aXK3 ziH(_SoWdGY#Rt3>nCDvSSBTAGcyCX}i9%5zZEnj$23Jlz+2d!|kJxZz%+B4g=xouD|B`SY>m%>%D6U?| z1as?>jE-b*oR&o9$CZR(`X32#u!d2BaAzk+f-&W&6EOyX@Y+9Aznx~9AN_RYD4mPy z=NsKMnO%tg(e%IP5=Yc+1jW* zjb(W#ln?1C2w8-;)>^beAmybih)qRpCTh?kDneNiihwYR5`7U0f5c+8{xp;zkb^V? znds2mt;0tsC^iKY z0TA(s-b(6EQ)62nwZ^W6@q^*u&X*}CeGO2|4hleuMr{T*1pC098Ec zhRWrH!XY`*R<0b{iEB9okXVXc;fdu=y1d>TbyD$g6M(a3cwo(m#8#>b_$}rvwnK9; z_fb;hj?MGx?4k~Zq%2?ILz=Z^EdVWSB2O|?0Ju;DhVS*3l6}f{J-3Z%i<0T@S zKRx+&J8PIEEC`f4@Z@TGa(y+A2 zl>+P_d!e)kqD$b)9FSD1wvOZmRs)Ou_;^DH_eV(cq5v~` zAUVPJVZT12q>d7`8)yBsO&@myuxc&%%i=-U^>(-YoK6-!mzf$uZ+!S+XM|*-(f=SSo7s$Ze60#d~(s-6#FzR}5p;u~gO$ zc+z&Wi?w#^4~1=AK5uTu;a@AI4MCV-Qxit*+$#%kx-XJUCYDvX*r_o7OL!4JoGz;m zZ+De`U2IK}_xoN_I>D;sTx5a+scac`@2felE#}`+4ta7;9tm0!5_dTwVI9!$YRPIn zB%tQQA&irT<2&_v2eHiqY^S8=wVFBfrY}A||E@5^ymP>G{mF4B`d8!lstI$m!q;E4 z_)Q?49sf{4Th-Lru;yBaSsMFtYT%k3FZ{N+0X46Vm>zhsXQ#=TA;fb-Lv41-RZs6b zk-F}EM&UGBi1*0y+}NIqd&w1xCfqkVmeGHD@}bQOGCj^6{Ul!$l-@8sOF4I=>hiLS zW7@jr`V;5(-H@hGUx4eF9JZtNZ=60vo1c+Z;IePA~^RzElx4$;5osJuE z*$~{bb@j&DxbyCOy>mlp#pwe_@h{!NeL*|^8@f2*`MhIihR<)?)VFluPZhhohMl$M znTHQNT5f+PBE{tn$?LsFB;2x#=DTs|*8SHHkDk8#;lihVHXUy)_HP|F8rNI@VD|F2 zD`s6DdvzIoWDxUn@5E=3%tf27%wF{6`oV1`tj5K0Mjwy_zW$l_t5aIi7(mVGk@AWYg40v?~4ftvrJq@o--Jp(Ts+bp>q4khE_i24s#G;h)%oSD>T$2?{oGl)Ei?2w zW_-STp61&&$ZFH=_`xuAhjE|(lPzsxY1@Ra{w!*MtNFzu*@>MtGdKGa2a25BRtAhu z%M9=;2=E^Co$5>eT7>&T_Pi8u(fehM6vgli(HEt*@gZR%%GaS;3M~XoZWiJNROBo z9FfOtI;_)>+;e?Q#2o{S9tEGu3~6n$o*=STHsp%w*c8paJ7egaSRGET7KReq_I^Tw ze4kH4K?rIhX%%vK_+{NYe2AqUhz`OeMJTe{`a&$^uJulADKXH1CsES{slP4?Ph zK-)3BywBSe?jJ1>f5uOdnp|lLyL&F|hQP=(8n?#5A`dpX$HR}$i55F}lXYaZ{Y-4$ z813H5@TZyK5~)ydEsLS1Nm!;aC};_o^}H=Wc+jt-?Z1u*XN~FEHGppZdDpR7Mg7%; z{*ZnY>&&Y4(BO;|&}d)8uYG-GK16Rs%ra6JhGTZ@I&DB3!v-Rfi<|bhk6zI~GRM)QYr-fg zfkpOYfSzL#ip*A@0kL^nx&DNML~H};>iF%KC2qu41tWhJ7fj>pO(2SVd02B^-fA^i z%f*hH>GY*z#hdqwk^a>VVuY#P?%9hdK>V3GLHh<{SBgkmtHg>y6D_Lavquc9#hv9d zF`_r7qOfj^y_zxY85*nxSKmdC0A+Mlezwdug@twR3_dtYKKR*^IF>kS#06T4PF^kirNT>vG?q9m-MR~mGm z+}40q>g+Y?nwLbxCOSQn;uw^sslsd`$QY_h{&fL5iDS9gi&`)7@GzwVcIg_Y7lt^c8NSBi75JBam7T(O! ziQao8Ca}(v+7B>~AG+di)k6lz@}?ted#(;0&1&Ai=|hpWrz2^G@MCt*88L0DmU)f) zJkH!&(!KjrNtQD=w&K@V#i|Zc=;4cN4=nJ%AV&SXzbFc9(B(yL&=8BNvYS2iqSTF5 zgX_`>AWwsiBm`HIyZ|Lqf36+1DnBOLcPvb#Z1E>zGbZg{-mBOb-;;H5c*`VZ7$Dkm zMm)xPoOXNnzT2nZBg@InttTpr&)eV>amh3^Oo{5SSZ^J3FO+7{TNgOXP@I>e@x{p( zCwYzgrk7VVdRE$?7kiqdlL4?P{>iF^FDA^KcFps03L>oZKUIn@bpo`WC0Kdf>^QXG z%YiWK+Vr&trbBJ%wfApaJMnXovZVIdq9O+>oa+!!w!mlM;`IxEz5n28!nx5MKMp*) z_i|m_Pz_Kt9kBap;p}4N=T+$YbfEiA+=a{BDUVKuYzDgZ+dg-mekY3i`gjqnoA~Ua zo%3O@`paEqqaMc}2MjXq4@9~^f|f;VTi)OlsgL{bU^^BjRyy|Xvp^*eS1;l#;s{)<$Bor@_+>|p z0^?JZm1WUKPSKk|nQ(O)c3KUZ%UftQK}i~#Dj_nH(+)h!I?A+6_76PKc|55Ix~3$Q^*8I^{OviQ`FF<(K^@9Qss= za*Tl{a}<*pklFpf|4K?i6>GFp9Ksl+=1G8tgY2R#lp^e66MaV)OlRlize2U+oVXzr z_*|r7J_+GSC$i8;FKqImynJVprWJFkFssKLoG!y9otPKHzmoc__H>_|T7<&@JL7&# z$-MHkzGqh6SpNKI;sdEj94!+!U_8t5b!gd9HBFZ%L1Ypedes8C#dsdQs)$upTOOty zvwkt}*e+7Z9EK!;h`DJCE^H9Dc#)BY2{CB)EC*6wJhWxQ;;l!QRotxUynEb~mF2R@ zAV9Xbsvjr!CPvf4Vj7?*sY#98z>k$%R4)0!S9;7LH9%D=ren^uM`IYNc|^nVnZz1Z zsP~~harCtxRM@F}+*xJ`$pKeh_pD;)iqn>rkK5hcleDAKB$}5VVF$Qqx^%i=Ahs>M zYZyxh_eQcHyEEeX@~lYi)yKq&07a@hXIGIrI**UdYXxKY6IJyUMO<7A_o)*god+)vF7KUgWch7GCi!9Vj`KJZ>8#@o6wxuUlI4p-eidMv9X?@{E2Z<{8cBFEiGve*&eBEOFMJi6iBrL@(k%ptL@a7=Ln& z*|D_a)U&mi9bZU#Gc{2c>WL|g2{8a>e5alZEHd!VTb*GL!&)gB0~vHMl6Y{+X;BeZ zlq|z7UAC> z{FPpI*pBooY7jX(THuV+kr{a%T)%1m#sOw`*daatZVuSOeO+;PA1vHRjZ&M$%wFEY#6bq&fK z5YZJkZycGVok#_?Pv(YvX(<{`w?2SXBp$krUT}*yt4~~tK?%_;)87s(aZ?ZkEuo^H zsY3@^^obRV_^UAstA5yWZWejF-^Sb@`KDPZ!)irXZGuD9hkWF+LW!=XKfvf*m`5p)|+d#-rBx( zXd1`LynX1{uh;i)MO(C!;9ti#zTEoYR(Igffx{EG)!g6u=+=V=5!*CN4Ii%g?cuC# zqg|O#?SC6R_FLcn--b_Zdvz@HrO9uv#vdAC)5qzF51>{5XN|A4CE#jXB9BslZPM(3eepdG9#7E<-MM39`+qtXMeS7=5|PDayoOM zCUBQ^SYx`w^TArY~+{3yZkc3=(QqbCT`x;DeDj9MEM5Y@@>dMx8j1JbN_OFyOL1~UK;`K$3)H#Ai`DKt7)-MfkjcW@^b2rMFH z!yHR*zG&lA4^|su-f|-f|9HJrzH|D!q1s)upwq>f5!#KYz)UJOpAFON2`2tn2I^m$ z_8tI=OrPEGH%%tqHHP`mB6kPG>?zSh*dzTRO52(an&Y)BzYve8F)?IO#2nbSHpu9f zq(tlLLxhr9*YT!m2&{~8yVFp+%inAnFF>DXUS~G6_97cuX+^QaWU+YY^fdp03EV*U z<+DvUF#H3jw@Vp}uEu64cfKM7vBMnXMpdWG)fMet3y@ zSm*tC2>V7TTYPYpx}BXOg+{yBKPI_j!se(S3XSpX^lZSo zYII&8J6Oh+@dCN-FT%Sy@42%Flgs>UG1UrbJ*E^WIcHF4O@Rj(M&VwcHdGqnlpQZ$ z*{v{PhIq_dJhJ@C^pKHUvNE-Y!ogeRwVO?2?lgFqAu-Ew+EpTyI!TqX@c|Fo31^Yn zap z%8ZNlB0h_ftnDhI#riG-InsE(9x`}`-J{ClaYl?N#hj_njjU}1*7^sFai1n{Af3q# zHr@|+62l|&g)hU6ZxnMZli%j}_SLO#jGyEfx93c}M4l27X%wAQiX-bG%gR)qx~Zfz{IDn89p3BwoBS-P(V2 zH^}mYmvh*Lrp!4m)=cu_4(vA15~9gH89Bn&pEfT}Y=sw*K6#`u9AuZKgfsIW>qtxu z{4NFlY8!Y_;g3IOPNeDk<+|RPXB_p}g7BMtp@+slw(b{+Q}~!SVWe%HW)E;?Z5lCZ zliX|%3(>Jv9%rgWi{tOwccCY{g`+^~{_Fm`Ls>z3bcXpYFaLvHWrePlU8OY^&dWl6 zsfr(M$Fj`x9FxQnF?P-@Xct(?|Ig` z^tn0`09;NDFb)GSdP`K<5n&9w(Caudz6Q|Y)?r`o3uQwG3;jc}txEbu2%_P+zj&V?j(~|<{e}xi(Go? z{r-j2E;rknBSN{Cb!}WEp{x!d=A<)jRCX5BLn7-s)by8M;^U=hIx`qW)<53Sukqa| zp@${!Ibk7{xF+QG!M}t+*Cy|-9`ovI1b=AR(P-+pQdZ*DbV&qHwS?f8u1?d+F`p)Trzp3&9umVPN zzqq|@G+S5A4W#{?HMz)vV%Rd`(pw@w%S<~db^o9nJwa+8*agytW*ZOO7r`B;x1SNj8#?v*>*E>Bwq`P&v8l+9+_~KW*;?9TQ=IXQfYpXYXxX7EV?;l70(u{Q%NI?*yGt}wem+8X zT4Ek8o^veY7TlXuzEjDWY?gJ+sX#o)(FASO+*?Tqlxg|9vG*gMaSK2;< zZVMZN3~^XJwk)^fmR<(3$iNmNS<7ome6MhS%Xmog3S{5Xk3k{pek0GiHo&yV_~p~F z9Ac6IeHDrmrbKEfi&@4G=@)r#fOhN0-SDll&adRqLfyJK)(52(P5J#)y($gSvKVK$ z_e^L-%@F1V{CFxHsdG$6lB0hc9m4yILb<;ft|4MCCD~>BQrq1I#w(l{?F=;t@ey36 zYWloh^Gq_G*O9})!c1ZkM^dlxa~R&wPDY({B<^z1tQ$&?zvzVIkY$&V4il={pOD;% z*XVfAk!LB`-<-uDcOg4NNUhXyX17DhxP(pl%w&;i`b8qzYp-tGUJgzu-{~}hByuFW z0v%nX8H_t1OlvJ+lW3^inr zjbBm;H*q9-FNNn?OzHEWE8Xq9+W~ixK6W(EDqNx`XuGQjatHkUX>!^<#H9c^R^D!Z z}{3fkTnCkclsu~!;G8(S~u+ocN${z+VdoOrOwDY z>c!?Do^G(jg=c}aeMk@0XJkpB)}B#0Pj8h!w(Y8R+YZxGYLq0IY) zg`aIJG6~)3L?CAsfO{MjPUGJ8~Nr-HWV?Tm9hu5v!;RM96lP8!$DLnGy)Z3^Iz1`egs0AAv@!Bk!?y%iU zom6>F1^ywI$2$xfO4VMwT;{bs=oXz$#+kDBq!^}5G8e(TOK$uUrW7K`4;Wj-u!%Z4 z<&ZwJE#Q8yf!9_2ZvXh@D)?pi;s3$jdqp+z_HElUlQt>91P}o!p?3{ML`0p?3{8R{ zT@6i95rTqZpFn`nG!z9DH53&r7y=?HYEV!F)Sw91{su+F4vLD3^5(vuvYz*QH@@}m zeH-6iRNt0VH9+CLYSk)DCGLRgAF ze7(v@k}YPdKl=(N%TyS!<&a=Y1@%@2W`^F^XXI;{qCi>fW`U-?G-8I61ERO(Ak8OS zb`IdTF;CcPny-9E%6!g3G5|!(SFvoLw=3TckA<0qZVcs*bYj=M1loEtnS^HlgwbHZ ztQ>E#hLw+>VP(;WRKEHMCVZSr>y=sZu;W5-mRmc4leus)#&VKDvOWzS->UMkW;z~8 zWNmROU6V#$)9r4kVC9AuI<)e6^KrUoiFdk=sl#EOaIiI)r!YXB9UMx0E~ zS-Gq3K)MaY>NIwS0n5Mn5e@~+*2=UJ+rv3BHBA&6$+wloS2OXzkO82F;!t!7<~Lj# ziTLJ6-~a^15Pf7BAwD~7{vagCt5)qGW|-)1n8Al>4}Az)!8dz{PNTk}x9Jt0d)Vby zI|SwRd}11WCmhYGjKEa_M>n`{(U+~nV}(5BK_)A#mWcNZ$Vd7BJpWk)ux|Ih8%_b9 z;cv9{z)U;An&!fGAKOe8{4f@>B(qlyxV+1Y=p#a_#i{PT}cS>xx z=T?qr4EdwUJoM~n?rtAEA*~w`>|bLmxLn&4Q5+j}^zlE5EWi_}wp7k}WDpxACZT&I zr*TeztG%DUmtfTeg74bwhWUcRDKg}V;w(LTME)cvz}FRsY4tvvEyuZFIM0<+Bf}-u zA0If_?XJ)#DxbP+QII#|^(b@`$h8}|=XL#owV@|{v#ny83ay#Vt#JNq)nLi zt++{>r-#pFn|5AmJYQptL6XzUY?Hwkf>(PsN#YFy`gzOTI^*AsR|`7sZtjc=xAUs? zHjY%NwSD%KeDc-I`=ccT&oV=X@SjHK0z&Voe)iD!23+r_V9oPCyE~FK&&^sSEA#J| z>CTMY_1ah@yAc3CHtvjLdp%vz@zcGLuy;7H%+phJD{yL_HgVh&83)|F2X)6o^iqG^ zYlyFyz&ma>S>&+x0~4^YIYIMb)THhdH2}O$VhVF*TI2T}1iug7v*!@Zmi0dVFr)F- zf$5zF7})l~X#l?KSZJah&drOIXy<-b@MCKSK_kVuDH$~V!YufbjHPOuIC^uif0p1! zkF5$j`z3z`uFWnq~Y%yN?#d*t?1@k&p-%Fx7(p}*WwA9il?~g$7++LzxyHaQO{_J%hNBxW1AQ46wLGJWRkuvp3`J+vc(~|!{L;= zCtwFVqF*vMuk_!tKcva|MaSZuUzcU2u|MNo-jBPKHkn<&z4%Pi!r25?R_6!ZCcmDh z#Iy6fGt!p1OyK+N?c(fk+;PH1$;xS`M3%^(Ime|8>H5xRGQlSHPo+*YnoQfo9~qSWtk+Ny5My~uJ4&9iykQ1CVTV!$#|QuC*6OXT-!l;Kc2Ww@aUNHhP_)^ z=eGu3Y}u&cn6%|{l1cjdBP}66zIW?6EqjXJg8Q-a=C)l;`a7FSoS)8r*V?k{{f|9g z?Y8axyCIH4FX^f_k);Cm+TM0uTH@U0SmbInWT` zUj+5T|7fGU$a^&x51ce0=c~Em=6{MSUfpKaxF7Ss^dAKE+KsKR z1b4+A{~@lp5)k-bMZ0-h?t zbd?-IJ73#(qXBUL?F{Lpk-Ol_%EaMp?X?xN_ek}&1>vimwg}MJu9xi?EnVJjD)rnf z+oZ}f>Ji{s+#a*oG>($AO&oi*UkS9E`lYYz_uNYN*(`tno<)HcPCvGr2CrfiXsRBx z?12bO4CQN0S|GYqN}3;O8{F}aD5>S`=n5t;-1_cqHj0Y*@!AWGdgywTjsnyGnDBGd z)tqTQZJX-^w8MX`c;Z4RTbUPz{w$N@4)59U?;egr+#Bki7Kn$izSyXws~SUHDU&yG zC@tRa*xg^&eg^u83;f9mq|007U9Xg_~o0cbixwH7h_GK-&zWC#| z`l1sE(J3qV@@pD!W8fSC#+*M2^-~ZY5W9H)zw)<0~ z=E|koNQ!%|#%SM-K}tMD7hwPXF;W(mypXjZuMD@pWqP8^;{5cN-UXkhzsi@*`!m@emh$J@ z(7N+~zCYXc`Ogo9vQ%(tq&(&C&#_bI|Ni=L@$=u`|0bv>2UBMLOuacj^LP6Hs#(z7 z4ALvn;aH>YzIFn0WxmI!`>UhcY=K%;D6ebXOC?AST4oq9lQZNY z{va}EJR4SyV{U4ydoJ`%dK^GxVmLj(I8>7i`@rrx#EF7OCb61(9i#_mk@Eyr)qWM0 zotaEVKBr?)wZ5V=H+&-D`}W7V#~(ZbWg}WQLopsY6jTZGLqac_FUz!b1(DB zryQp))UX#NCgwFTN}7at?1Tsnl7UyJ*^5XHL{-Kei#;Ws7K_E%r1&$q+Ltgczcw(Z zYdI_(U#p|y8$6I#6)_X!!Ajd+%yPklZf1EVlUp=3yC8)AMW!H@6dV;Yi0()x2@he1 z1}0CJ;xKug0I`^gVT4zA=}jqv3`1`>*`9823i0On64G)Zo*c{JyX)d< zS|hAz%Ly5<#9HJY^G$L#uAKsJ#;@NXfcL{%2llnwWV@-GE^~o2@S9;lT8$Jzvghc% z#AVi1H%Z5cK5{O0m@Wt+G-68^sV@-BL>nKK3U8o8oLn~J`^B=Sb!ZEU;E|OkbtgLQ zN5u$rSBXi-&<&#sw1B}75Fqjy+YS+v5WbSJOw32s%`cY0v5YeecXzQ_bgBOV#^tdA zTP&5N60g}h6)@+yCD%O$9?`5j(2{p-oKfYna%K%ZogR!XCuFE3c zo|>yYJbT0zw$a_}v0qX(M>Lj1iSK(QkQfD3%VC5sZ_?x%U2OaJtCL%(z=jX0Omn z`NJ=ZX`}JO#T%G&*v5FNpz~mi3S^IePE;2^AWS{PFNCY#df~iyhxRtgyb}$p@ z|JIq^p#?t5L1*J#xYQ_2eFx-aMOa$@8sC1-y@i(vWLbP9J$A?BSuc%36by@bu=3{U zMWzA1d-hx!dmLgrTDF*@#s=&`?D#7-=11TqKt%D5CnngvEf zdbb;w8&>G4z1~FS179v8qq%$9Z|nh>J3kMa80T*3Sm`Ro_T~>FAsu`FlxJvTfUgBr zldatygT1!jLpg;-!xmO6>9J`VhGXigWChO`Cfq%;1f5`PNpUX-)3vjB;33oMXn)NSZ==N7Q*0 zdAu%RW$V0sF5U-Nd3@nQ`Gor6jQp&jywg+W$KgD2j6pqHy{<0rOnAZZ2l-c0^IGf+ zjwBXbn<|iL7v69w>mtPUG%`Es6V`DAfsrgqG+AR?2@{o zf2N8Q+WTI*>>CN!e;&SXtO9*gvG2o}Nkgl~WlPQXL;EJD475DLz3BT{_WQpL?OzoH z44%~dII=CT%##fIEt}Zq(!PIXWA@)E5vy1iUWjOrb!UtKn$@gDaIv11X76y?!w}>? zL1WvXKs9KQ$t8^rb=;xcSz=2hW2o3cNBe*?GPoJoJ+oEh)?Q-%7}3D?Ta$rpZwi2Bt0;PVg5Xm>nrm8>%`~=q6qfn=hEUm#TKge zg|n2CWX1>AXv01jQiml=!kTn;ge0&Blo_OhhSK-hq_XEG+b8sNnl3msc*LalqD>1TP-h8B4ywBpn(jO2taZ z-+TeK%G3z_KDpHepqjr?YEYmA1!Dz9eI`DxUK2LpMTWUR9g24~!#(1fOPMOOV^ViB z;b4X(u8(x>8$l{2p&02&*&!)LPFY4b%F@`ZoL?~!KnZEJ1h2va+__=6;uWR! z)HrM8G-fQ7;M*BW6VCkS?o^#+(F{}DA~XTdC-z(lR}q2h9jLluf*y8+^cA^#gLq(m zz#-I#QXCJ_DMPOG6CARO1nw78V-*XPB{Zi*ke`BHMoEE!@U6;w zI2RNHtJzSeo*==U;8G>S!Uc6Dm3L6(HHTd0k@s66(UQ%$peR2!w|O(3_2vMjF~X;Z z>LVf^;2}38dakjfDFM`B=)b6U0_y>Hrf-&r_nC*AEq-r`J*mAWg=(p~a_gW%w56MI0X=$JqR| z2PpxrZ@j>?m+U1}ox_0;+z3PVM6yz*G(pM6kkD6`7b=N936v!x&GklXWy%l%M7e@^ z`<>Pm9i*m`!@a3<<%TN%&60^ zj-LyiIkK5r)nN|O4xt38r1IX1i%>Dm0_(VAgSd*W>|_#3kW^~>Mk|aINRp4za_hZYzttgtxy18C74MMqK=m zGhF-)UKfEcImA32oDeN#+<@ANHP+O-^^$7Z#XdosY|W|ZXeiE}w0FEmS3o9Cn$bRNNTo*;Rg zT6ZKuE4K}{zt`%#nvuG3@bVd)4TCL$eC6vQ)}8YQktG0a;KC*9OkZMy91(DdFSsh# zn8>qTtD)g8jC1_bGjyR3QgG{_Ui&qu9XEaZs#LM2-G;gc!?%xs2QmDb3r(|w)>ojk ze%Y$lJmdmhWe+ByhzToP@CLug31Sk5hTkk(xdS6T)T9MuBV`OigDVot&4}ZeoH%wCVIPIHsWOf)ibOkvVaDIT^#-~!Aj^osFBUSnM zBLy~lMT1p?O(yn6zXqs^Z$L>^urT)CbD;YxgMg|&5TCb&&b8N8Y{!YV?k!;+^;)fl zfGi7ufcnisfNndgCK@ud&d!~V#(TRQ$48WvF{J@Qp3?<5#|LwRCzuxtbUY`Fk#2S6n4%|$b& zhO(Yvk&LIpC`Sfo)WPkq5WOR#Ql1z7+N;cCFYqF-S@J-CD9-r8F1-h(#&@s+caWu)p^Od zTY5UmR*DHuYuP~!uhSM0fW~;5ddzlO%+8d3=;7+X6JvBAAA}<5(Ex&F7|z><;M|Z? z;h3iL$##*z&^V-cZZ^_rsUA0^-n7g^lb`YJFJf?xWRF;$&Mltz^CPZtCjl6JwU|jc z^zo)PNad*ZJfD&8;KZ>KL!-~y#l`x?pLIVYuhzY1fcU_^Q8GHNvY$Th z^Z1RKMCaB9ja6x14d%@us`-{Yr}xHqp=y)shQ6xNzdEQDmT|72{I7d7_5UoVvkWFJ z0_ltPPCm{62CK&xwNEl$%<*1xkY(_lmHa|I=v(OC?;7pj7G9sT<~R~N?|Zx4pCI9vApyu%dZ=`;TMpQ`sp~eZn7CMy5&(-L5Qq zdfpe=vh?TS>pv@B{5&@EQ*8LFdg-s)HNQ^o{Z)7T*XirO&c687F!M`d_*=U4ck`Ox zEqi}opDOurPN++o+BxLuH4o6?fONSZYya=A&)S#gWlf9$*JgyW&xn)#bZ1J0`|;_a zr8-l69~);nJvdqOel4*&bL`K^jJARF&(-E<^5ckD`>zyrH|O7ay>#Zh{l|W9dct^9 z(ejyTH7$p@A8Ii(x{Yv)|Fb_YW-91u>GroureD!Pd@`?5`OYDJhHmPO`7dVvtkE$m zBrdO{!&keM?^p5R0(p&gOoj4wfe{BvRjK1$|6z*6eIzaMl557`bZ(zlezXTfeAB*t z3HAsj#-?zd7eA646|R$-yVMRz*j|M?bDS@m7T{}gj-eY&BOZ+Slv%B8h=2UxwO@sO z!oAvvhob?N3)4T1Jbw5lP|V6!HH+*Y3##?Wb9a4NTxe%)8~>-ulBIQ8Q`&5s818j% z#yFzi_l~uZEv#PdshgZ{+H<(v!}s;Q6H$YoR<&*a_UZN0!Ov@Ce1h7^$7L^TbG(z| zOCAvdJ&X(*K8aq(RNIR~pV+yk7WnSwve%c(GU3_4F?zdR;&-C`mw~wCQ zl`}kU0-8lVknu+LO+&n^lcqxMB2Zf{-vC718mcx@RRtits^#T-=0>AYDee@T=5cqL zV?WR1tFe%AEyp0S4y2%IvP`ln4?PIM+7r@zTZuIo0f@PoWVf@$GqpG(M|L?!9p&O8 zcGz;w)Y>+VEz32p6NBa45uUI0ndUFPwoUyG3-sq)-VYHECv`!zvZTCQaCn*2m{5@> zgp|Og0w_1#!p_9C9R9o*{(h5t*A6!;jpI(8Kgsd*vsQE!M*C|lK@##6fa{!D#Sj%? ze`TqN(}Y2ViMCc?&>SVhNHS(CmS_d&iu*m|4y1=hZoKeqpgKBwGMKX}t&0yB7p;Hb zYyTy^*GWG-6}Eh8N**}K0Tqh%-7;InLo~aUYx7FvT(x(+4z*_G+IqGu$MkVWRkSswtt#9h)xAb@PGoj1dPC@Okg5Bw z?g#4oz{EuT^bN)4d!=D}wvP^bEvcbtQ%=of7fvO@Db{)~W7S)%9(ZZb^JKkM_w_FU z%|2O5Pa31sOhs*rR!ZWB(f1c;_DnZvwir`HUt?4wWbRr;Ls_=A402(`Nux(%INTC) zh!DO)kJB@Ky2FY+yNtZ48xQ1oSSvEgjt0IZ);ZRl4c{Sb}@0HQh3ognx!SIf}bZE(Uv;Z?sHBQ zI&@^PJuks%$z(1fb9ln}*Y547%R zvjV`o?YE(fYno|s`&?oeMD=Pz?9E8dsBC000aR>2%0!F7%dxu{~p+2l^k>6GJ6g2Ois7I}RL$fsjRSwY|-^mfT+9 z2^7CpOCaxz=v{k|o8IgT9NTf?&W6zD8naC;dW@>>gP|ABBy8HKuO2ygE3~EQgk47F z%BW|fp%>S*eN5i{J4!(eYrScfoLaOqdc-{JQcptimZQI;$GpQXKRl6~es1Tg4{O7& zJbj(K?dtDU<2hk%ubzvBdN;2A;(IRceL~94IY(Ac>Aq`wQ}p@avz;%#4TN3iAD*}4 zhmGU(%OBTqGc7Cjc6xIw2vgVKwUQhfZHQ>$-~OqHmu)r4rN$-Q_;{C>dwo% zRbR6%O|0y;d|Es;WvoHDN)28=72VkZ9B?LRXc&Ua>ajCvLrd-RP*aV?1NB?~O<{1I`m{UO6 zx#HAybr4>N-gx_f?|q~e*Rsc_YD;xqjzp%F?BOn~dSsrt5pUEmuo8) zOF3B{#;EB*(_j_n#GdH6D@tPSd3j2O-j9ZV2%IXe8f_XDs9aVcdlz*4W1q@$xiP~k zKKAnICQUj{{r#IvPGJT^P4)3jUA+p;_2s5JqYlX&e9@v=;%sC3 zf;%*K2Gv~*5L6!Tw@tpN?w+w+tLeoBPG%MV@wsZ%$_YRGzWuubMI9++tFGDpl3X5N zRQppW;`;JkOy3Aj>?)0kD7E;;oJWsANSMO@Y36QW%I2HZsY%#NBc?8me?1~+AJu5x zM+$d~2){PS%JXVn9MwhOhzQY)ybo2x+*Z?w)|`c6^N4Z0MaJ0=X)0j>!10PW;@KRjM zXQ(@4#1S^x{YKWUBYDp}^5}IqEs0S%V@YASelCwy-i1+rW%+cu7lyk=$c%Esvu+L7 z+}}kZA*T2saHWA%S*{iWwB5b z=q+bat=u}iEZbN+D@Pme;ZUAamtEkZx5OHXlnZn#^Ja*A^2zOdq?ct;mc3vcjOGZu zMMrX4!S{!*9zSyGK!(6o)?PN$HdmUJ?}9a+fkV;ud>&vt!N14?t&@Z+5YJp}aS1}q z%Ww=_SotYDPbUMTo`w^*SCiywOA>2#<_k7Cp3G?_Mua;QF`-BiLEAwP#1rl~E2J{X z`#h!47#5l+qz>s6h2zyxyyYb|Q^sx56Yq#2!EzVF!Z|n}b&-Yy3*2={TU5K9s6q%c#oMk-NQ(~U0jSFGQ3KdIe+9y7j5*`3Kzs9`rkImMY&bMjhvt7EcYvUyu z*{<$(9UeK!5&=F#&4rmiw*xj18_f8J3uuS=c(f;NZuCBc^GLAIZtMQa03? zKZZZR>?#@W(!C@uOA`7y`Q(c54ZHHzSYFJ@(99QJaRnKL@@xal|BNN0Z=y1Nv(R56 zFp~G?u(OtC2*NByI%469DV5b}uz@jE#g=TCc7p_zMJHBgg%>YWRK;r>G|Mw_4BVbw z?t0Bj3*nrYZk?Om!VO8nz(oCsA)F@7HNF?-j-y6WLK#mbLUenbPtVbswrh!cg;RH3 ziU}!VI9@v|*NI{zyP2;|im*JXRtDUxh1HPEh8j4dB>O3jPi1F4upzG=!|vA#)kWD) zl7wqy0ymfI6aINKx>G&Q6 zFXaot#8xtK1Dsw)_*;s*!@LqF&erb~CRAqATlKQU!UgJ`rBm+Hw*9r1jL8n6{>Pk< zXgB@vocCl(2M1(}1txb6ab>=iY_EZb@P$&m^5;n@BmD7K>6yIgS(>eOfbjDJjZP@r zz;vJQmKRaS|AhsXz`2gtJ-u1pWmEDnfR&G7t9ylZvde`PxO!lP%2-aMJUhNMr?0kR zCZC)2RO9;FjY2mR+UEwn)Pxd++2ITv5=&@#+mYhB;^m|;M|3k^aebyNn;p%`S5|w6 z3qmUM$Ro7P37p(q*#z5)J#GkT{YmhZ zvx55f2KnLb$Ht)-F^3Zr%an-y%5a=(8b&%NoL@n3lDW>4`z%ZV=b?f}6Ict76`&CC zO7d1z1hqyj%x7Z6#Opa-nAM|w%h_4JLTvpo=H*6qosI}Z-GgoNPRaxRniixc@iF!Xwa{)&tq~CtwDn-7PzOUFpgEvHglN{^K5lxc&lN zD$;F^_?i*JyHI?srHgOJT$n%fWKMSG)FOa9=3nXr zx-m@uC}5Z@t)LvLt^BGv{Pdj%K|W z!&%iqb~~^JkAhdP^3Ny1tD@*SiO@?=$W=KlK)@LlA(m1vPrqwm?}AUY=P%44u101* zybHL#?fno0P`LqJDr@q)K;|YmJ{`VOhYw9!4bjxv$#_c|8;+KYc=6DAu6L%-x2u?`MS@E8&w*s?(2NGcw2oIaU!u zK56Q888_m$qPDyTR&i-1D;osZ1=r_FpO-tfV#Ywo`iqHCx@SbCxW@S^3P1xrXTpf; zsV}Qdf?f0WQxEUw@d00+AFRM=;TT@@sx%QByZZ8IsaC#a=!uGEF}-K5`Ybh_%vGnc zT6$c5s!P5lM#=yvY~2L9WpY&c!C3X#_VYLog?LFzDF&PYUz8v^P5p5$G@I*xcCXF%vhZps-mc8S4oae2Wfg#q;d z_DQ~}5S^VVxo)Pl5{mV8Kx*r<92fh8}$F|LV|H~dADy8oHmx%NC| zp_Uq^ex*gl(d~k)A*nC9q37N_THXc~^2ff_A75L4-0yArXzixQ`_2vRITsj}^qlMT zd|=ZF?@g-pA0MlJdeohm{16`e^kEj1JiI!&@38HgoQ<0A9Y&8OzjM|cX-NKXFZts? z$)7$YfBu_1u9`B@P=Nh!k-b)>X+Wv`f@c7w^n@)Dw`cu3vd0r+xrkwC1v@qN|Fn_c zDYXp#KOHpp3bh+!y^j8m$e#78?&g2XfUk{^`}mK}Xr0*by!*nL!Snx;0V@U4kDF%K zFKNra06kGg_WoNSy|2j9Pq`D)w8T&O8M))H{&!?gWOBQZ-F_YeK>W;n+e=Mv?*B(* zZ!m`7qWF%Z0WGVZ^mC#7G?X2mLi2>fcpdjV~&6R)~TZPwgtyY%&q{68am|C>2$ z=KoSclSy+PQ)D6Dmcs(g(0@nv{!4no$g%wYtW;7N**hi9v3^%qdvL?M|6d0DKV`uG z@9L~|4L?UKw|)BgMw~nM*H~?N@~^jb|Bmck{IsJSYWpXhD+L(8AE|-L3$*3;H|*oZ zz&|paCIC#cD$I_z*;c%Dl6{pg-7g3G!ve2HHj+{r)=)riKFg4X2fudDdoiv5cu;fq zx@GJSo^}jl`J2WZ) zpxzkW^O&VY^q2iPJlIx%U$@6}y&`4$%@Ts8EW^?>hVX;Hr&92He?=+W4LDetP|l!< zyzpl1#TqA>**3j{xEW|1;45OXe!gdTcrywnsz!1sdF_xY2ZN?L*(}X41HCU-3)1@X zq-^XJYcU7YT!Y(EjVLO}a-~iBQg1op8V^(39$Rv%i zD^3U!bGQZOf9Yx_fIF>Y3Uqtc&H$z`BTXU6j>c!SabBSxkj#a>D5Gs0z-=BXe?F4B zQ>7iiGZnXu2j+@?md3DPR0O%$1G?Hm_zRlDcVekawNZHPdZFV#?RjblYgT-W(60-E zaX~q@-l)txEm$kMLJ6>e0`j=@z^?5l4D1~;y+_ZJX|mU>BvP9Nj0Xrq#`+1vGx$us zAjbC(GE-AJSrD!$F^5mi-^35#JC!l!Fg>kQ={U06=XshT7MCtph#b3P3lim+-2(P4 zmih|D36Yn|Mn)EY=phczk`eKnsg_-_>QLibSd)QJd3?$o7(nh-kSeXSD6?hev+5LZ zM1@TAFdRpFCJYMO;1mrg##mp(0sQ8#rSr-x+|`*Ne&{PqU;}1J`7Ean6n`CIGs(up zh{<;GaQrdb$Q^lQqcNJ&sah9%B#7^iQgIA}4a;{t;vKLz($&5(Wh%PvR+p1?X;+fN z-Z&O&&Tt%vE${M zUHYEf!Au6q*dod{Oh_d&q7fCf#cNG%zh=%%gWjyf=;X(9uX6R-1{iaYG}Tx0 zrT;{Vx6uEXw;06L%MeWBgX?}tj^-ZINRanthKLtH#enm11|hf zrmgM`q4dMtU`wZ}~oRuZ3oNjiI`*O)AuqV<7xF5;@D{!A@y4f5pY$-G$2uASr+ z9h%Yy43_HGrhEo<4)Y1|rCvM>kl)UEh!1mNWs@Q#s~g+%$BR+yl49x< zkVrw~oy7H-hy6-&bzl(p=)_upiM%^oc6d!8Al#Bhe>b+h!ONSUN^Pxg8lcPB+SaxsDPar&`KvZZ>@v(Ig`Bi-QQe?ZW@#{ z_Edj>>ZPc>VJk_p$|yO^?-_x)jo<(06a4cIO58h4*8coCvwYf*l9FMP&Ut3?b4R1j zaqhlw^o3~YZ?Qg;PVonB654;G4%ZK!;oXAT$@ji~nme7Xear58`DNDV;;Dg$K9^n& zoL&~Mww2$1%%ruPFmo_{>CdzN3ge}dehGNl!B?{xKYLRac;q<#R7>3ZsiASn(SPY7 z`cb1l-pWy7b}T2c|Hg8&{3EaJPHmIRy)W5gi8inPFpS>9`N z6UY+q8i6n6xhDsKnjBD4PTzZR6^_5@LTPe!zvle9y*4YrLUZae5on0QQbiPOzk+NO zqK)9%%=XMSVntX`HSEJw!_$fO?$=|WegP86SW){O=javq?j*}GVe9!8xB^)+Dh^BL z1BZeZJ^G%|mbGJz`EHj)izuaNWPEioAs{gxx>BcfJ zR0!tI7zOVOLD-gvqnY6E*WJk=cxH038uMTFgitnuFo`&jBIS5~UI{8^r7tcbU$+AE z_=NC*9OX)NIv25*&pD2gOI>npH&a%NX^O2X^O=d>XrdGlG+QD;Oe9TnjSiS!8;W>o z!NuE5;zfE^kyAGw7jQ6QrdSt>Yt!g_#E zDKCWV^&Bx=mI%jUA}eK~QqJo9acU74*&>fLHzGM+C{k96m%1e4gGf*#!2=ONdB&@` z$X*1mg@Dd7iZ+Y*+6kE;P|LayU2!rXBSWlGVhlADE|bd1QZC@{nK=Ch_j-QCY!*3I z9xG#kJq+pw8OND{TtLGhkcOwLxNr!^<1>{imf1{1;(}Z!d5aiI%?c8SknO%xq&$^( zUP|8z6&CL{>BDc`T7lyO%~(=LbXu|^=<~7 z3qqavV-|9pmHaTxh>|9V0gzaVGx8im6qZ!FGQsw!92zFN))MA35!p8612B?rEq2<9cKsB zAM-_K^9qVgsb=Fe%UNWU32*VNP*&8bfNrP6v@J4%nR4aeAn^=5tvf42LXvPO8b~Q| zVy%=x*}E<>&RQ7&+HjRmmGXR}$Jh1)_+bU^vKahZu73JSJ-Lcn2b?nIRFzvBKk6hk zGgJ|#yQv6~ojxt)kY9%*j~EC8p;4-+LRUmnw*QS4^$@BIH^Nf!Nrve{2S zS9v2BQ{P%qIihT-jRXk9i?zajfrOf(8r$jnqtDV1ri5fEm>DmoVL;1 zT5=UvqvY$C0r;3iNLiM++vO8M`76ZP8( zbln9+A^!g?0@dZ)abR*85BOq3{oS#VKHUJNLgy+F*J(hvc9Dj-ZTSX>!60p%bN&8k zEo_UlMkT#DMc9)Do9(zZ`=zg1jCM|t7ypO*c7O2cN)NXKy9mgljOjht2RYBOj_3FBqPK=?TAIs&ak`HA9_Zf^%%J9dcCV>L(o0haL=iVJ6V-o zlFD6x4A_@+@1x$mI~LUbV6T1?~ZEd+OSHV9`D7LswFJ7sTtO@$b|A z^h`wG?YBkF+4eo{H5z&-Fq}ZPe?agPedb3KO;qk@e7M&Vyqhw7@5^-0_2@+{GchaKArc77e9R0_VD$GekA4**{DD9?!8iPk7dt|d;-+{iW1}h^ubkk@#hBCA01Tr zGw{o;+h^H55dT2CYVc~|-G-`xO^c0p*{P?lLelI8J0Br$pWS=uKeS$DDT#l7@5dgU zM*{!dj}8_Mm2pZRe2tVc(mKe_t++4JZB z%=~%2VB(1)VBX8c|BUYb2mkQy*wueNJpZTA@!6-lbI0{xL|5PKysG(nvF6Qyz%7l1 zlbZ~`e?-2X)A(tp`Ss(A89T<$Q(;{2AUS3hK1TcNT76^>QclvC)qre#U#lzGLsr#U zt3O-k-X=P8*pIJZu~1qUf-D)%`VJ~v!`9{wzq*J7=!SbBNrncDXW1!-tOpkc{ey6{ z{f2MB7m+FFZTCmD9Oow6xoBM5daviL``SydJ`cZGdCPCfF@$hU{pVd^wt>dVWMyO> zB=JTl#UqZ(kq`j%7$0$yu6tZ?4)o^#oym5fvTYM;|=OF zb zmB-%f>!SfDG&d`j%>qD>2OgHKUSA(@E;~hUyg8c-XYkr326J?U>cta)f~#8V}6tIYU-}!%aT8zEAr0EmBcscSF682>w17 z(Jp{a0Lh!iPwJ*lT)5t*2Lu)uToxD3djo<_LCyTq35Pkyl@!28k|<_8z9LuV>}S_B~&M96@mB z?<2t?3dADLn+G?^5*rv) zCz_q39}Mj%J9Qs{IVHY~W(Y)V;ty1qZRFchukX0yYTDyTkDxnxm0j=(wTtKrzf;{W z_VBbXiw8v)3XMtmQtpYe1m6J_1HZ{Gie_JyE&aH*_XoPYM>Z|PI7cjdVChBO9-ofe z?5U+wXt!^9(O5}GWi)+u;w2^XRkhY9o}5Q-d{}Ujo_=S8hJDcgV(;z$ng0L(@8`}p zr^7I(ZE`Y9%~@2^Hp86HNzPJDC`qMKA=PuUF(+x5lThX~Nre85c--#y+x5zj*Q;*8<#eQz4Vpwd(wa$Snn6yP z*^}5&Dno!fVdr}?7PzjiYC8ny}4}>`qRq5V;<t%~kT&i{U{<&nK6U&Wu)iUE2BL*Ri>$HxGSy z`QrD9|F%z85|$b{dW#GX$q%xDTtV$75vllEYB8{+3b_BD+b2kO2kz|@|MF;^reB=9SIVbId zFy#AwqTn348C}@eEGA(>NXp43GKM0AQc%MtUN&BV>ZsC~mTHQ#v@ovG9@Z~DoAbcv5@;&P2cmI&1 zkB$ZGJo){506R%FFTnTZ)N9fjy-lU2<*vOkEh3%Y}so9`-E#(G=0Y$Q$_lMTU%4YUHnl& zxU2}SOmJx>uDn|)=}U+RD$hos(X_n%okB-4QLU2nY%ILqK#xIL@pyj<25Pm!`K^85X}}A5X!Em>ba87Lvv6nma7{py~8&~r)q~3yQn3s zE!5XD&NvdG+O227P<|b*Tr#;UOK8|fk6$QG4)gUCrrW~^wKbl6Wo`pr(QsBqZ?8{{l zT!W2q)Cgs7pWSN&rpyp^+B)%4}};>hE($*D6#g2w9`4Sq(rR1p;=bMG~uI#yuKi#nzWj4Wg{24A-w z`IbwRVwC{EsnAj0=ZJOQB2h1QB1vxqyQ*Y~JFxC@Ho`1YS?RfehF&9J;>Gm}w(hQvv0X{;cFP;b^DGJ?FO?5MD5jz3Pt7O6+4`@Ql!)ApO*Ewn9X^&6$ykTx z*b`zniZ(HNde~8vxnH?z3YNtP7W0#KgzL0Oa>!GUv@L>#dg&s>v|hHuC&y{*u6C** zWUMN+t_-)Vq9be1$O>7F=4$^q3~MPM$*M|U>}_Hul4qR0iv&US3Dn+ZDre+be@Mrj zXX0xK>CQMk4`T?;kl9vjt8IYgHXE&2?+jWg{CqA<6~jK|oc; z9uZ$>tsd`?+Ko_eMi|3>L9iuqALa+~68Xsa0YMBkykRCxBf=aRNh0ZmM|wha83f2S zf27A$=Xg+(789fqabTC#Wyg7vznplOgPCxph3+{<<~%st>+O)|v4Vu5 zTm@!UL^0b^iTKf53`u{R`vzzg3-0f^gk)y0VG1J%#uAIy*NB8iPB>_mE$~wRbD!?T zLP#UffM4(CG!3YL`{**&O3>5u&jcbrHRjQPh_$;R1({4Wjd>6yrHdIRc&f$O@=dT} zASgzSc;8Au)}(N6mVhT2}3NWCl=)ovgqx=`xKJ#RU@nryEBIdXa&^v#h?Irm`jA9@k~qA5h=5YpbvLuPYDkKIi|ve!n@7BmC``PEJ z2ZE9fZ1a8qx1ajnhe__fczgGSX}x^3!pgi21S8}01H6}f(Q01teeYZZDqX)=#yOF`SRmBn5f~ZC3_uXAnb|ml>q3=sIUF^{0FvVJzQpn8jcJ`U!-x0ZGwy zi*W^)*i1iici^1Up%teXxt{W=?vN}HtQQo#b;Pf9?UHI!I$J&voab$wpzfG>vf>`m z{WISf;V`@Kf$lFr@8L-&XWYM~C+@uptYhHSqO0wJFi##iYl}IgeLX#m^EuR$pm*z9Dp5HO`LT9*mN%Tx==pil^UIv)KP#T|a$XDCUSG*x-|V~=-Mzkt zc>Rd>`kCmpl;O2p?DebG>vyZyid1?s>GgNc>pO3-yn5(q#Z~~pDqYKhtB%0l4Z^;A zp-!>T@*c1V48)A}x_Cs!gSAfj?YX3+DS>ZbRRa(4;g zrjTCnMz%HL!utJ#LzgK%0`La*k2gVnY_Ha6l<$SNpBS>fqiD9bY>YPq^kxr~shl6x zJPT?$YSQQ0MW8P6v(NfJV_0qO;H35J=8c3-AZ79%3}Lw%!~(rz73?=xQC*xVSA93% z^^H(@_*Kt$%+h!4MxE6VwIKZAWr^)ZUnw-hvyA@m(fm}u?gdz~+mZBLfo?RWmwMIdeur^Jc9&3TmbG3fEDx^_jT1Q68~ZZ?RMmg{_$DRo}^iY+_qFAKn`Dl zDaQAj`z3a`nn`)Ft;#vUkpgIUMPBTjKK za`|F`69uYFfYVH1Jrg?Rc!0{`5dyV>CIkm(3I+3RAJ_`c^Y9?^oRisE_r-!O6Z{?Q zD}pJw<>BpvLz9a*Ra>rNIUy)$37$BgD#;83~~@C=_Jg` zykMKnilbDNu4rhQhGBvSEz98+&6rVZnMj~cgg5?<3VFuWA(=`3}#*lB5V?{!U z*lt-TR2k{PcWCzneoZDy zZ`vZkRbt$$azSh3qPt;c*hYJ%(ZzIR@$Mz;?b~9C>6fOgiY*lrK-3kh6p~9EIUSVR z3|JPM9?E>Gqe#X_H(m^PMGo0#X2P-9aR+eW$o>0w?xQ5?6<;|OG*rg-w@8;&Ukq@D4e)x{$J%@Ea3_N^q3-d?ES?_)b{I*EL%!9`> z1;O+6%hJ2t3{dzelicx2z~Uxm3bG$YxESu`uA50Idp|?1{SAU^(42jh3f;>Fa6EU_ z)pkT$b{vmx@_-^kQ5(;(IpQ7>G1;+cp5A!%q`EEAk^h16y78EjZ#D5k&@ zqt~3?6RDWlvhBhK8OGA=mkDkra$mXqM?w_h5+!M!K(N+A=2v{$)I$0tU6`H#W{;QG z4wM2L&b^;VGOmV6D$z!D<(i(UeYyP8XVE2h&wrdkmJ7Iz>&nZVqVC-LB)MF9>E631 z(BzwxwA$+6JPKm$E2%!WKorRnv6IaJGrO3eKRzv4#)zveoNyqCf~{ajVrM{D8UWiG zntFF5r=@%UL;KIWG@xj#MH=buP4OD5k?S7lCj$JMprr}{3qIN>BTKJaP5rJ`gH`U6 z4yHd={vCNbBQ=c7s3~XhcUGqouzeE7fsfY?-Wx@5m^o1wc)6o0L6ID@=0{@|#2{ww z9b18-@R5iWIkj0P|E9bqGv^RiR(ql-KZakpC)KooYhQr16bt+)LI1R+uO%J$WFG^6 zl3SVYoyE@w`T9y1 z*4F8&&*R8}2%?}yy9{kERL76-9BlJ1;?Y5bYaY_(W1QLQ+a=_F=Kgo+&nFXLzw;1! zaWiT^!M#D*f$`JHkf?_PKCv%7@ta%1)F@bHX0|?dJfEiv0miRmV1%{gX zxH`#KT(A+8%4E3mZQs7yBi=8=g}aHHo^o_X#ywn8?W2@-Hk&*+rS|(XGKg8_I%5b{ z;k$12>8^?0wfeoBYNJpG5=j<_>ADMA7>u8-Yv;|2D&R$!WuzjYr3wVWfI43ZFj2?; z-Hc#@OQauL?vT1a$w|ipGD7QP=KxpqyV#dE^Ve?4&;kUO?f2iRxJLf8aRWz80ZP0t zTXBi@N+&(8epkm2V8QYk4*Vje7chYGw`QNa=Wich`txu% zpl-o;$uD7`n#0bkx12vSh~Os_m!y?=VJvMQRwY^z4DJV|I+fAZeK zFR|iCUDL06o0CmvPVVd*sY&UpMxFEs`FSkm%zY0%2SUh>BgxVT#;@%TwX6B|y%UEcbK>-pzP-Km;aEV#AD>OBhg;cz@?gPJLOk`sKvme|rATtDpJ0 z>&%uP-IKIo#w}#$(xXdfI9Kd;cZ!MR5`qfUmcs~4^Kt_>`sy!1VpRi|v+QVQc<5!K27yW|{GT$DRjJLS%Cm%i*5sJUqZ}V_ zwD0(;^W}H7nwf@Xcs0Q^$y3tMyBkfZAWtD+%25@ByS6}f4uT~R+qb)=D63h0PFRWi z#`%7BQ^>V5TUY=7U4`MN{2MUTB9M8EAiIx_sZ&2vC5;{rp$=LDi1s1+4`L(&^0PWzCJ;fx%NucFLOQ2K_Ikh3n3kd_#>SQ>O?NQ$z!=-9gyP}ma@_3 z`x+2Y+gVt{T?w9DN%UK!3cx{APJc*Eg>acqR+R4qp*m!?4-eLbg}`8`ivVogk$Qn% z*n6Z?EoX!43=2o4w80O!3MFpxI4debE2jAZ{bE0z7K;<2W=YdjMAmsA;athIMwseG;0>`(@ot`fp(aG%ffg9~}Fdu;) zCoOhTw^HVl$Wi)cxe70q2(tRH2oQi{;Yl5CJ`8SK<+eGZtWkOGPjl9*e&yR_e(Sh{ zu@Qly5BG>XEn1U=^PWUmD=@mL`X|8@@+m0;$*_U37NdNLD(s<#Dy}}Vfrr(Ev@Z0l zJ!dh{?mF!|C`051t0=C_qr5e1f)r%iMn*((dZb3d)MEpSobTSP^s{Q=Z8kz9l3Z*G z{qq_=R9F9l<~1+3-my-1#!9EWzk~ecerFXNfh&_=mOdIK zjzBs2F$sebE;?^NVSTxa@j)9b-Tk!zC(c22S9>yfH9P{Q6`00sH5YCiWF&5^mcf-_ z&nn63V;Ulp=}s&uT#svL7un{%L;-CPj)?O@K0@^?Bxngx36nT{+1yc}sdDG%*sKt5Rr0#A+Mr`Nl1Wt*o|X6N{Z!o@zK z^b@8~3^i_U=|F^sQiCd}YQk0|?vU*r?-)9YK?s5VI1ZLQzG$%T=r86@H>AhTU6X`t+} z3Ai<`Og>2JZ`mK`V(1jk@QVpuRdB5S;Fd>B-wK2h_7^;*b}nQ~!&(AzqQizqOuzW( zjY`A(1$rhi-8cG1M(_)hfIBsLmde+SuK{W6BT6#@+X5Gi-cE&2;gkW^@U(QzgRhRX z=-*0e%MYW{w1mX;O}%uiZ7zcBDHbO6>Z!>Xm-wc?NwFXFQ=_>tG=N-zUME`?787{Q z(?xbam!_62xJd5K8R)%NQSm@k6>66|lt0-fzkUJKin7K2pgtyD5_MS@*Kx{jI!iTN zj+Czj9JIFHzH6&FU8W|)^iTHwYWkW?H!Y4VT47qfL-dLo!sq2r909G`rrh+gRdGkS zNZDo$4*|s&leqsHtDBS)zxUKf?VA?YlBc`s(0dRJ7&TXzdIbW~VJ5(^4_(O0GL9lS+F`puAKq(|Zi- zTcir(MRH=gS~7ElS_?~Zd{A*iru~tAHhWtk5GI!t-JkN_R%lafEs?>bkg>+HF%`3r zDPKW{t#=CNFGRh%fKSJ=54cl*3^fqCt-0+y{r)mwE(@XP$%3FVxc5ZJ8MpEIGz$_` zg(&V*JW1GAIM@z=eZbwHX0p@p(vv@QdyQKlv(H0s4QzU~@j=&8L^;4U91OYs#%Wna zj}%7OueN*58K0o5i&S;fc^COTUtfLbgQ{rQq10bmVLSioYabQDd|OuEdGdMG)0(?` zp??#;Nv<&83Zl09Ehkx;gLG+6wYd-aPlK3taqk<{Isv3i zCbZ4S4||&*aU(r!20Z3Oz)CGi!@M9 zPD+_;AE>21>f;)KD)$fgqM52sr*&E)xcae*7T^OUxawsF(8i{zNbE(rA5|pJ*j>NV zaysNeJzKU#T(=V9&CFIpa0{~9P4Ew?F&$K3L>+0dCozm@*R$nN>f0cZoUpqL zVobZq9PPmfZ@ibTBG1@33XhiOkp?AhJU}5(h)TpfE%o2%;!B$^)sF`Zm;gYKpcv=+ zT{6j&?K%XdOLyUvMu_EIcX>3YBk;LfFT63MFo{X@5URQyPaoiDf?P7ZJ$Z4T$#~jg2(`GNeMS{MQB%2(~9qpqVx0XG* z3{=9dI7d}fr30d6-SdH zaWJgxST$$=dYa-0fL@yT1jo=XK3vyFf14{EvC?S2I4@gyJMMK-jYHx-zM<-GizLC# zm|1J&JINs>OlTx1Q+-HS!3rNy10c#I&N@({&4PTIha!HRBXm|@Ma{rDC7SwKT0 zhL)q+P7KMf7J$00MGoW=@*ub_5-`Y^#PqjeNohOMR@;>3_$l{z>%wQL@Nq1fdVi8O z7GH5to_GB6n;qI27CeG|AD}u3Ve3ExHSNF<6pD8ahUXlGXSa@FKGFsl?q?Q_qHlrSR*h&g>OVo?B^DL1^LS+9`c2q-R9|q!H5zI<4|PLuNRD~C$9WV- zXvmY};eLRf^!($=UJK#FrctLM{slZYh`Sj;m%YcU^_=Q1!|^lRi+1V*k32)tMJeuR zr>r^04F+zRPjDl8fcwFUJJ zWuelW``{xt-XnMBnnE*Pyx5yHjzme#t!})$!4lXUJg~RDDbWqu`*%*jo274aFzH0xiAM+0fBMX9I2It4-dKggnqp3vAHHKPNc`zW{uyv#^ynGuz?>gG zmgt1-Cyc^=#(#_Tjj_3T_i$qV39hN_SK$e6`O)YnmI*etF-bR1ZY^*29dGg)d2nRV z26OM$DHZf-=cYg}n`5gsj;J`E&5}2+`GopUZbw{3{$QGs?V*P}(5*Sa*Os+?XVC(_^Xs{7AiIn|WwM*3{9X8cU6Ln=``V^Y(A74_rE#;_XzS z(I2~z8O?QNUFmqssL9^?@}6@9hcs=M zhKm1lZ`(f6_VDiElK(?*d)Jp(lDKADn)i+W+1n0E_QeF=)qGw1{J-_KZ`+e;VnF(2 zKrCJgC+LPx_NI=PHS)5?-KYQB=(+>CfW^O{tYw-10-T&@wZ#|JG6~WR| zVb?HIx}N`kZ@XYm&FsHbEB~MMwy&%Dfh>su0!#Hos)Wb1D~Lv7I#g@7sj^h|i(+

    7rWqLnqN2HrnotT-l^xUani0fJnZ{3>so<37yZu}7%y)5o^&Ncw!t1?w`M-MRmJ<@vU4 z))io|{{gsU5(p{2_1Jp(vE(@nSe?Jo0iD`0vLF7(nyd7z6}8UR zL)mWn#3O}7`=x)7XV=}Jrmz2-J6$9%ihmE=p)mO!ddwbN5I=DHJ#_1>g3V9$t`Pb^ zwpoeT<+4YAgTL4mz1lMKN`2?Hx3JlJS<5r(U4J7_y1e}Q-dU2q;zhjcp!GUB`(`tl zV))Bc3|CsR$RjWdX@n@Am= zU1z;G?6Y2`E%G5S_3)B*KBeDd>28Gjm6xn*J zO>q~sEL_svA#2sw7<4O-4Dm>}gEV@2yEn8foQ=sS*Z#ol+4O87Gj6b4_h)a9{jY^{ zpj-t3B?U}LnR^2F3O)6{UZ+i8vxFHH`bI8&uEAfk3kE9;ZTk8=6Taq%xfTPU zS$Z(p-pXK^_En%4LV$@TB(z42BxO0O>~`jinkx&y4!$mS0M-I^8?ml9_Ce^V9$XjX z;K{MX_S7PPvgv`j5A~v+Iq(nHThj?gXi>q!XeoO`S=j zAd-@`U*D|FIkeE*G8*FS|-b1i%D^te53iZx!2>`k^CaZmZxev+xcMZCDR=p!)EEm zw@EK<4ZQf0@bk`;{PnmVx1Imk|J?EJ#h3Ue122EH{JcAV_Ilz6w^zTOoe<$isL(H_ z0R~p3aAyf{=4}F3MLSe-%xqpMFLkN~DpjT*hzaSc^T0HzqCJ=r+_$Ds*`@|XoE7?) z|4gXc21pIA`KcmYxl!qroFdEYu%YRUO|~Zc*1EiU({Ga3MY9KYQywpvZ0!y+e4Noq zS9k}rH^izyH+H+|8d#m1d`&v1d<3yj#M~tpVR}^g>eg#tz2yi(7c`u*^I9m-kAJTW z*H|@4VM`!miJcoZcR``0Z5vKF?Ok(gdB{1Qj@iJ5IEdb0b?f$R{ypiid21!^ZYrhx z*wTk}u*wl>?q+TDV#aK%0nF~6gyV+Ac=|qj2>SQybc!?R1qtUcw$b}Hvjw~6>4bsC zMrYSQAGatFB2NKaII`}IGILYP-D~=I^(n23ntqZ!qGJu#@hA-z_bH6lf&H-^`qblh zHtI$sWWJaq&u#=TQ$Lz4H9(@&uPD#F;A?6*cx;ysY%4&8z^eesVh#i+1|#OK_CX>I z+2@mXGQ=M9ZZF*}p`5sB{U6k{UhxK3O9@GFd@4Kv7GZnmtHUIn?;%b< z!aW@j^~&|*j^n>}Rq760Idp2=f6d9K4=xgKz1scx=!4%cfB*d{QM@;w;)zM=2P^M4uP%?AySI4B>(9sStG^~5-22h=;Ln$ntG}ld@2@WPc%9y6U3T-GbH4mo zNv)imzKs4pNwjlE*gGlx-kyEt2%aq5C8~goAvJD{#KG&K^cd}fZ0yRVGfhhF@HPY& zz`VjKAA74&Pr(Y0%fQU{c8z+DQ?cdU;D8c$&>Lw0?0fk73{(kjG<8}Jua!=~l338H zOYv(;p%`91AX2Gnq%K}miV$gU3>kt(YHIw{I2`0N*cQQW9);AIoOn|5o4t1A$1PddPZHV+< z0hJEW9s>JPB0j#?4m*KWj!6?YuPs!9*ykLFdT$$D*maepk4J535}27cr9T--0-=8uk#In!@Da`|E5{JzaqM7S#QE%+H@?tPn5^_~O*p%J0!58C4v`=XV&XNt(JHps zyDTV*iM8|3aocF@)2w6+u!C$77HpJ(1Ufl_fM-Qk6R>VKa|J#~0R@X>LoKOT>6{q} ziAQl{)dkSaOQ<*(?5?0t12*i)zk9AnDA?MWykon88a5h6i-xy_BkBvB=T8_AFjge2 z>v_}(wv4MWHmeSyDVEiDDS#dJG?+T`99Ot&rXZG^3Wq>60G1K~u1!JqTVk~+aJ0}C z+U?73WN8uNOM3HQG?>ABd_$@cKuPnwX?HXk2%ZKSpK2M@?ZQw6_}9J%hH zqie^G-!;a*yp@A|bO9E0(bftZY-v7wTXWnOJDUgmgDJ`LDXD#Z0h)F2cW((iSd2^% zqYK3{En>N6VufE~tY)e5rczvRsairQzOYoYrBwS_sqU{*f@YcCrZWBDGQ)&2Qel}% zOBwlDnFl9)oo4w~RJlczkyUVc?2&R?6QfNnG%>xd9+@FYOJxv{xCH8z(MjL9ea~m0LrxsvAk2 zQ+c2etLvgXn~G(n8tI79cL>V zF|sM5XUlRj^jv|;#c-5)Xwpex4(e#%r=;L|R z2})s#4Yn{Jv%gRIrnm;#CT}2tKVZQPC`q~tGVoPXY@itH&Vup^m0q);)t8abtYS;Z zl>`@W{~&C3P=P@m>?jNRgn%86$KRk}9plmJvufY+&!Ei}-r7I}`>@qf4Y0Os)3%#3 zkTb;k0@y+$EDK3IydO3y4{w9QN>MurD#s?QWR(h(f}ldI#&v2vjCLb-G)MOJEdv1F z9egU)0ri-Qx+YZwiO-J&L!;)AAmz5f{57M{>seD#(0XWcT~gaeCFUKdejKW$26Sbr z4~d#UH>?1J?HRxqNsyM?)$1k5&~)qzmTXJi>323L$BkGE;Xac2Wj-Jk-tl0>NDDAO z55$zkps&j+2Lu)5vY`)l z!?t3p@=l(Dc1Zh~(j*bk5#ECu+=MQ)1cQ5%Yp^Q7hUcHSpGB9_^RU?)u*~fcZJ{(? zWNX&BW2skBS#AToMm1p_+#Ie#R)G;9%!ls&w>8va4j51c=||b}mhjmkb^Q*ACZ$bd zgz~x5_duXp-ktmLhS;>{r*M=S*r@HVplTo{EduW306^@&15;O&+$-#UcQMu#W4$vV z5S1FpRIs&D6_f?hk)^^oko6AMXML^JfcDVWK!EDWiD|$gLky|zk|m!VQLFhGia*#& z@e8A*96cPLoONMaLS@oi4Q$>*;BGCFs|fz3mKnW9K2#%HTgn9O+kLz*F01c*sYWrm zuf*LtkKDh9*1s6qpOw*nxwW4>Dy&W1z>0OeHh66NUah*xzRMwYrS2-SM+dCx2XxH` zGt0GlY%S_nx^L7Dc86%IcWNAlrS8q5KAa>>v<|&o8Jf}_rYT|nBbuN9_y^xx40jRQw(hxEpt`qUy{uZe!cJ_-dhWES|`HkWViEw*__^URlo7Hs+?7qe;>8 z9s)W*5?+UZv)eSk8!m>Y+)|Aj-92^ghdW37; z{?h{c@;{=9fKw*HU*GU5Odmuy-?#~0+>j)5YRm0?AIF-Ho-AhjFkwY|=HZoH5ANOl-II8Jvv@Kj z7OZq#kZnD)Yr}|)>byGj)X0myLQqy?>0(5>Mg?C@_G+WgE@R>e31lrsU)?iW9;f28 z&diqOd`_u4)ECDffOLDMc}*{My%ShaCN1*qS=;y3Z!_(F6oe%r&j-FqtTYY`JN;+b zdF>hco8!}`R}894&TX>}97!sw6iLVHd(a)MP1ZcBVD{LO!GT8keb|(ZL|$R?J{kSB zG&i4pA1=hbzx?51;*Z%6C7`ZInAt%VpAYUL6{IHr7g8=tx3n^T_2cD|jdKTTp4VOdv&pn^V$YS&wy%u?kqmu|Igi}O`i_l%jx^HK5GR@o|nMYqvzYuMz!| z+2RO8{lMbrzb&xtHlG(CvYgGnkNX82`2Hwx@73>*Lr#4D{)C-k_G2PE|G7Cmu4movxyC2|!vcHDHztMp3e8ASMRu`$w!Q7GZ-@8BJFxsw81D{}>}6KJlFt@k zMQVXfbEZ`YeMaCRG>VghZnQa8n^H>{fkF{qkwn0<(O&K_x>1IPjY1zHQ6Vi7$;sDl zV(79y@Qx+LZl?p6CgHGb2GtDNCarSmbBK{tm@X)Ux&u&^NXAwT%4@2cR4>uY)6>j= zAVnXPQYYR;bO(zSPN2_g+jlE~DiW7f*JCOdW8IS|BhWwLc>1rtPEfZ&g2atTeRGmz zo%U=hv{zI90>;Bmt{m?eVQccDk^sd&WW2YMMiceE5sA`hqS|cDm^7O3%hUNLjV4;6 z&YhM<6PAcxnlze7Gtrku6HL2DHUEkxF6tUfqX{2pQR}~=iHq(>{&zGXz3A1Yz?Z3L zHUY|M%+9@r>s1V1h6Fy&&bu{eur*g;q4&NP+{Lw_?8WYT*4;Qi zl2~PR!DT4Y|7hXkLBkDA@k0lr9u`eTzgHZg4=W^!VS1FksQ%QvqgCIE=lniwe&X`r zc$x2og;*o|PsXbcPUZQQ%ugFpm-ju0fB)?w@^Xvg{?5G%JT}@Tj^gd%RDpSd= zrdua?XR3{ttvMFS)Q}m?;5L@(cg;9?1&`&X;VM;6?Q`4Q?~^;`SG7TBpS$lz?}9?y zr7agOd!FfkSctT~WPfa*Hz#Vm%HsQFx3eETFS+{FNOu@*a-+62C2YI4=TS{hc$J0Y zd_T6p8m@;o8mt~O{D(GmHRkMbEDp?*swyx-yJ%pMq*FNzk5k zT=n9qn$9c{HnWar)+)4VD&G!-vnFkmIZVZuaq$iZX&%-s(A6plLBBS^0xZ{gVKCf09s?!+dAF z{dRxgMT?xKxl0ZLPI`#K(KWxPIE}_rgOQ#$3ZD$Vw@P1L9XhT|TSEa}a8n8qdLn z63&BdtLxBQ@Xt{Da!`6QRaXy#kG#MBD%eJl=2cVIKVo3ZaJe5-TmtVjeT0srNK}zZ z^8W;2Q<6^yz)r!$v==n+Tq%GPZoNv!jE6!>Qvfxp!y3I{c?et?(4Q;^w}%lN2w3JK zUKa}+uQ!#W0_#H0{9^&VPvz?LIBl3X)X)^SkrZPl0=M9KbXy)Hkhj`Rd>g@A+C!SI zEhZIiNCkwLVoO<^?91f9fp+GH=varK4b~8%+5%`A>sZCWf`E_Stp{Km5 z*iMPMJ3AiE*^+?Wk^2*MEpC4fh|z^m_*l3c1h^j~Phrcn38FWF2}o}#Vih9wc6Q{X zKVa@BiQ&5S7>f8Lj3#}%$@&qn5;SZJ7Gv9d_G%T>34le{p`fK`3QIvGglqv|8wqFO zqv(YKwDj@SapT-fGmJ77|3wgmtAo6?#A;J9Yq2}DB(f9${skrl=t07>P!x(?GKdlz zU=3qXW&q5by7fMgDjO7~%7%nNfpQcqOxnF>?WL|tvay$y;PnX2I*1X4YnB@ItxdWx z0#Tzy`{qGZ#WVm5+_k}u?8RbjMTHv#TOqK!>JY?uCSC}5X(;aHEdsW2yM$i zk}43FcW^l`MkD{o#{Afz{NwTYap&_BZs#XH%|E@I4{8)}Hx}@M3jQzl-ovYjec$_^ znM{&N2r!{WniG1JfQX=|6Pk#KK~ZUHP*fC@h=pQHAV4T$Ktw>)fC?xm0SmW;D_rwvf~h^$QBxh5*vtlo61;Gb%xVb_)uN zpV3?R=tbKN+Xn*os)n@^ga(&MhwoM%R+ z2&on(SxJo#^SpM7V4>QOqs%Q-QTq(-X#0@GJmM-TDkp;Fn3SWYHhmgt6NXx0nhfst zUb$vxgCymdQ45XKBH!-VPmjaUS+X6Q!3i7zSlUAxPbHP8DM5joxAa3E&>9XeorA5B za|+^zF!cK*V%n8)(~x0<)>zU=o&55pw30_@JKox&lQ8{-yvofX8idTVoy$0Yqm@ds zRFq8tYqzV`kpj)Cq_pt>sViKw1kHs-Qg?;J4Aj0FN7-Z0l9Jl26x&CHK7h+0HZ5== z8V-a-9_ozmzQ_bRp$VUlI8gqbH_LJ}ql)Rlla;fGBDme1g zf<)?ZDfHF{?N^aj2-kMqG@3l1;f~gwx~VUu42$8Dw#dOvM%&Nx;I_bF_H>pBr*K_? z9Ab|%v_qfRqWb&Hl9fOf-+LqnwUNb`)H>*>%7x~B9C@;|MuXpbCOvvgNtU;_E(i3> zG7W9Wk}`KgZJ@CXyzZ&LghsMf9`eDn?sD~J^aEe*(1`+gq6Y3|V`Omybb9KmqXgdj zV3My{Vh?TdHle{9+6(5mO(S`pW( zli9j2GG@=3(cj-L-QVRSX(lqjws1)sOh6W~Hjvg{|Q7%lU^ht0&K-J<9HvF{RFpa8Z85Gv=$d|!F zv`RfY(k^TpX1?sJ?Q&K7PJF}ops`+WLvf1Yp=9xl0> zVY5JgW|-5pAFO+~1_udwu%Byo!d5uNZ#G<*iCOJ^SQzB~>|r5mbybBF8q^a7kbQ}) z(1^FT05Fw2lbI1yy!dfxY}MY!W$}%f_qPeo*gYvR;`u(1K*KL3Lfkf-b{WX zWwNh_W=^%<7h7qR>0ZO1f5h$&*8OS2CyuI-tTFd^S};qk$q%gSy5W>lH`K4V+Ibd4qcrY_v&z; zd$MDr&dS#KvIw4*N<`Ak6N{Ny*Vbo zw(a5Z@7BLqm(vnRwjbya(H9b|n_yYlW|Fj)qp+C6c-v{YHU4+jb!>Y1wu|$Vb>jjC zE_gH*4O|?X@N(c%?nTGM3;)WxxnY-ouE*#7wl-fR5jX~pmJ>8-bm z&yH`o6qFy8sto8F@mtN56R&@JqoAIA^{Cx2;p;%BP5sxmxd$(NeW#r0{O$eKS#ulT zU0+oH?c?pFH{S@>b^bp1aBIT%&rhrCzkhkzr2XNB$;H>-zrOEH82Z+(o6zqUyyqKx zXi!52Py-Z@|C9~!y8sfjRqyjEqMe*aRM-RB=vtT3Slu$p=+3sW(e>#VqXL=O+@@Wq z7TX3@&}VhF8?>r3I9^e;^8!v8-Bf2EBzu3)xOyRVmA2$46 z*U4|0o6CRL@QZDx&dK>{!~1k}=9MRIk>^=CMt(jw>D=4m=7$eSEWCwsC~I2O`8}Mh zl+M{7-)`M(54dbS*FDB)MIv0YXLs483rjM4O5lQp+v?6;SmwRl;j^ctz*P=v#sb!@ zY@&{&lg?sbfH4O|)NdiPaWQ^`Cj%G&L%_BY-R)oASK-v>KKt{!Fn@yd9nlL}0k6wl zcwIo|I{@f=hpDg3C7Z}ZP72@ou;$)?Y@|(N%b|+HHN|@yZPZvO`3eaW7xCWKQBkh^Dm2>w9nM>J&J&I&g;m_p5d`Gc0(M3JZ&( zI%;7c?phRlZeQ7lI@4oIrZqmVD_r>jn41w7_Grw^Y94KW@`X6~nmrKp;lrfqYia{d zl<>5;e)RhceXh?ega09`*7Yv*35Ufjj>GZ>f|43^hZ1~rM`L(wnk7-KY zlzU+1HQ5!!Ys2>H6)@|BiK27hP|5 z)7;ngi*=)?x9x4$P38Q-y8Bx=62sm7OoDZ7g9ozw>hAXyHJ|3>H{>m<8=*b0YCrvA zLw>?nYop%=&*EB*2jc5yY<@l1iL7ssZtZ?(J$Bd>gVx&{b_6}LkNkW->|$|-eCwkT zYd&}JwGOg>+ghY8`TUD@UzSa4AXrzBf3W3H(zs)&o9=U-6jcgN&CsVGV61G z(LY$XZ0?3LML}E7hpxT*gLOY1uBx+h8;^mov|LWG?!vh#*?7&_@&ZY}P9m$h z-SDDX=_MnrW^pZrN_EbIa9@L@E%hTWF87*#;0NorgK7XNF@eqx84x!7>t*M-GLq>a zm&)6bSWL{P-=!#EOie24D#-C5Z1`GDfaz#XUxoXc?BEbK{J1{i{(TP&| z1^eXHFaKi0@9b4l{dpqeP*A(&%({$Zy9E#b*@i#)$q2%S5ECI{pNEaRJoV%k{o`MU zTc&+9M@3b=Q-{91X`uyk6q;x5!o%?XA%G<$!oYBtkdiD!N`&-gA)`;IH7G<`A{`GA zDxt^+*0D-NM$ICVK9SiVQFkG3a)a48IT>5QZ%pVgn0M(~TE9<#xh)i@hAAA+k4fns zoeK-!kygvWY$Z$qK>blko?&8tRt%s7+34~&i-0jJ#XMFcV@5{ujJx$eMl7Jd3r6%)%g^x2+o3mIH>-$@&Q-oBZ zg!-)G2kUCC@LXgu{a9~?WN1(#t#O{f_vfd%7bItwu$+rZvdjCjpPDb&!OE%haFTiC zR3+!sl;rGd&Z+CmIWU;Bhz%GvtOyY%^6N9-m%bANtf>5QoaS~5a zl6T(2$iLlC7xN^<+RiW|np_uAFuO`N-V8Ik)~0XVv{A*%C--c;AC~{PQ@?z9{`2Pi zmwoxfK|x(q_l?J<_hFkpCT|)n+4QA()7QRD-v>7V!=#X>6b_eCQl!W^YaT`{Pa!QB z`o2i2vsgR~02wm!M9jch4kq#>x}OUU8?1}dv4dPTVkw13K5i_W$F!;_wEbLI{kV|h zx!EmzvwO;BkD=1dV_G(Q-Pk-nd@1+CW^d0T-|!;;l%jyrqQI7-pc_TOpNn|IwoLQf z5*of`X3CbZ(k&4!TcU1kne%xIe^~K6&*GTy;)N;2v8BaJT8iUu{0-|?XCm8W1nb7^ z;t&JuFtnW7q~0p|%ptI$q5)`1LQ`zf{`U@@9EemvMJa~l1~R#=Bztl0L>XA&0>;Lm z!!W079O_ghje{ea6p*Q+Ou|t!RcV5{wDxJ~o&idr9AXHAt|e+dQIH`nc>xEd$~oI` zEu+O~dG@ybu^P~FI$W`BJ6khfNO9z#{ye9JYIFp)>HAQj#x)*I;fbEa5h0FN)3#TH zQ8&tIw%B%JU{^Aoel`m20%>yK#lt--X@CZIdSYsIa#?k1YxUZj)$6}h3yo^h z#?*)-YBE!6BxN-@tu=W=H*500)JTo?7LM6l6tTBBb#F=8-m=!cwWPI( zymt2U+Ve|{S{mvu-W+!Mi2j8ybyow?*;2|h6=;m_bmky)UCgc-9e`bEP{VdHm&U`; zRchq^^SY53nyG;5d8ma9nJ%E8`N9L@6y-^azIq0k`ciOg>%^{7&AzvY~1Q@nT$SCGR`f9oz53%7e#W)f#DEAzT z*6>l=FO7uJPVD*9jSfl91H%=KU)hFL9119}A}z0g`j0p(8udOwSu&9M8FJy%s(4NF zf%+&pIZHv#2_y<0EK|D6G=NJV9RT zs)Zp88L)YA3z0B#LHC?=*mq*J33?IXE?7nddisw) zKYHTjE#eJ-LT!BV?bws=BTs%@b8>Lo$uCDwe!X?_`)?VJ~B-957~W z%ECHwY~7jV<($GR7OsAY8tnj8bu?-;Ok$rHrbG>f(0aH+V~vUPIe$P~4Vv}5ZWE+6Wc_w^tg^=ciq2qZ?o4TEQghodA&Nhqzf$$|ebTtodQOOIds7quH z)iN4!3nW%W-6{x%d!R8&&GD*EhzSlHIseieahB34fkv5YQ6m9$IuD-DK}O3UTLH-( zfbrT@vRcqbpm~Q+Pf#$NP%UMY^{~SHXsL!d4>fyXG!uuey)+6@UCflzvwVLQb;9HLtsBp{AE&3fcNRtpt>^7A=Je>YbHcwIXsl zO1sW&+|8j@amX=49O762>WM){X!YCz;)_ffja87^=$DDr7XBWHO zbJ$UP`xpAH0L{#lJ>`CZb`NR$+X^BaK%}+eg*0!Bk}gMV)%42(S@vi@aJN!fYaP#R`z@((t?zIVtNQH&E&;&m~|qI0Sltm9z;NDj7My^qR(HOFf>J zm5Sbi(PAf~8`K(dK<+Fh>&?C?xnNzdgt!3Ip`sJ#8GMDbQ5Yr939T0(6YPzymbM@| zcC7-^IAzb5M!JUss(JxVE4PnJ)vN}f@fhkQC%^1wKuoPxltIXY#domCq2mlIzNRIP z+T-XPClm=pZ_70b8=?K{=uIi5+LO>M!LGR>kh2Qv=V^FvM8?W!Q`VX4aKNq$Gk91ce*zOt?@ zmv43eNR;#vY57fO6_{{bKORR?$1$@pRd@q!j=CYSc7&x6*@1=EPlI};M5cv)TtMn@ z`jN7s7OXP8Xf!49f-8uGX>j-7Y4$rAZGUHDauvHVBd#rR|HMFx9#Dy0xv;Z#hTL+kD zGz9?80j{3|BYDr4h(=KPpoxr#fXaDT-$;&>HWhnOjTE3a@T%my5ew3x7wNaP z+8ulhm=qWz9NqhoMl8WGO7!l^$7b$ZPK?UDs4u59u_RA(0#=0!6EqF`izID`&9x=3lbCTnpfO_nFx6wyu)?+;K z^pX>`MNWZc(+y7DPiRzSB zoXSEi0?Pv${;2gVpHHzVe(BMVRqpz`S`X7M#B~r@be_W0a}zXfp0;rl1jh6|JNWY) zWPbizs@oG_y><`Irr%D!MJ-Dj~|^vRh`Nm8vX{bTK(o{ zc4j>L^QZm`pO#HMdE(n|gAO|`T{?MkXz1xHs*1Rp8_SQsYWhP@Gz&FN)HHvuT5fB_ z`l%lb(3w+2cR;#J^-`DE3cf zVuK}5Du0$KYzOGpRzOmpVxX$0@T0H&UEO_ig($VuK5@y>qWgB@EfLkX5g&l z^rC??rd#V8eNZp8u+y&IafP>u&FHt?9u_dz?fEGPjfU>LJb1}SW8BJ%<4pIv#a(y$ znVm5mpoUH+vNLEC!|l3N!UZBbLtUHLXLvG*$j&_YExl{;&+Lrxt(AYx&dh!uZujPK z(h1>M_I1@af0Rqs_vis0w7BGZ9M2g!zU_fNPkHF!<|lF7?(P5EImW-w&M2tPU)3Fd z>WST>-~6d3CVczQ_cwZ?R5N4z0Uq8k{y?;}(VzLIt1M?&uy4qOufH9=xluQoMKM37 zoG>c|GMXOX2hLqUws9i~;qX&X@BhQsV91LS7;^u&=b{JIHM9B%KD`z1_V{=Z0-D|C+1b^Rd+g_4k-wk zdCa^BLWQ>(XFx!r2)eB!A~7Ci1P9#Yj+p2Z@ATx4T93zw+}t@Zz&dTK>sq!Ppy2^2 zaQm61*9LypdVHokZQR}g8L-+hL!E$hSOFTa0!WXm{LDQsTS|*8eROz}(gY3IwwIQL zCG&KG7$E2q)=7=wNaJ?56QOJ$w{kTyqmfwMM*tsJ4|E!0nHkJk$U=0Gx%PX}DN>s8 z+pIL-=60hv3CuO>53+s+S{cPk9KNbxHw32MjF)j4Di~*7p^1{yHL;#4W*^cRX<(Uo zGk=JQ@BzrEGlu(?Bh_qe*Q|>oIeWDs7O;o{fj)Nbm~^xQq%00wi zo`0^Ka>8<4e)^}Cn@aBFZebR2rsJw^)QvEUvvA&9-R(x;tf?j|z zvAxQU1vgqQ4H;2|kqVH9+R!tRu9pf9MPf7z*$}W|A3csPhM^lU>g9q;+&MKfh^7{N zx!WF}XShPYb7FQ{cTFo|*nkBXGSaT4_2F7oSb+6Ij&#~ZkOqNb-Mf5pl<$2SDcVPm z#mz+#OM~&F>Tpg!?0BrLCgsD6jiL70iFYeVP)oYJ!fP}e zPG`z?AmpZ&7$OV2*RnAw8|OnVQ9`Ph4AeA{*+f}ca=l~#qcaJ!=7=akYLMB%hagWN zX=KOQw4kO24Hq?6O9?ro|usl+ivK_7}#NcAr|a7Z=cLS;}~*%gE`O99D6{{v{jeHS4yy`lWC{C?=`@->61a_PhcwNOrEg8ZqrRhgZq`nSK_5j4Y#5n&ub>T z0vER2qCHubbH>e)#cqkj^2P!=Y$*r>vfOfbPAA1DFeeDRND|(vyFSGn*yokoJs4clQaxpH?fQb^BEZb(TUAX%Y;=eYjwQ3HC^7qdeT>ubRIr% zA09&v9w92)X58QY@(9IO+B4Fp*-qwwTwW($ktVnZ4(HgQ5yk4uG&gh9tUK(xPRYt% z#%7&xV4a~%NL%|2v-7gHJ7sc;6)SoqE*CDFE^h#1dTfk^4@t!7#WSB)pTPt3zaYDs zHM^rRst^ZkmCaW&m_)b*hz|Y;&^sc5vAY0>AAUPa*-P80A>EAxD3=7{9>7_%(Gnkfy%wPhDcM1=_RUnkSBMj3zdqn-hAD4gNlj! zq`6$!i4-Mq7`_js-O<{uw{BFOI~js?=R)iPG!Y{1q~kQId<|6y7U4mxLoiDu8C(1dopGohQ^N~5ksO$fQA&0j0ZGZ1?WU6nk?rA z%em(XB$T7vFnT$TUgJ@M1qf+K2F@2ES}WI53&c|zrjuMq4aEGLfDtSw>ya&0PhcVu zVWrBTh-hC!!*;LG9Mw71Yc)Em5Ah)64?^{jF;WCnV>x{xKZDYf^g3Uq6wwk(Ec90@{mw^=bqv~ z=K+eB8g725*ZP67%NA`{!Rx{->B^ZEeyCDP3&xRq@8_qF)PmbKGR~s!M-oCz?g|x+ z=qh{}3X2*iKb(7aU8UI{vi4=q_q_26jTdNgCa2qcmfSBgaYRIn-;uG6#3 zz=SIi2B|F)YM_r)=dYVCY2{H%RpWz&$kN?tt(=;Sp*;^_xeAtIGoc1<6OOv6lVDLe z)V4XW54}Dbec6E;1MpQ9Y>3hLQbv@JZi#N@wGm|*dY(;*PhmPmw3Za2sU-eoeJOz| zSlO4Ee2{*)lj6sjMlV2C;bPo2`~5?>T8I$MhIh>ypa$T?DlwHfW86T4T=WzzsHs#E zjPs`2qMCO4*PaykrsbrE<`Tza9c83T#GI6;Cjq*8e&j<#57Da_$wCff;gK^qJBE9b z+tO`K0=)7}%< z=}DsCky8Meo$X83T$X*w%Uk~TcROldKb3t zA~(=h^sROtNIv%j_)ujj>fd9RV6Q#S55AY1OM!tM_@clZ`A1 zNpZsEYKM6SN7yy1miP!;?J}NM|Iia*`>`PSd#S(ya9mG-QQ+@Ei&_6M7!{aj>Cac! zp>Hex7L15e!R>Qbb_;jd+l?a93w(+!VDq1M>VJWeMC*XRBO2}3OJSM=9E4wY{b1~s zeY3lc?EkrUu<_O^*^QRuchB#1HPzoEJ_Z_QLCy01f7(0vn^M6aVDx9u;*Y(9<`pmQ zUO3wH3yl6}?_kgW)xCoQ@&%`ldWXNczRiT_ZjLn|Yu{e*=48j|-xLZ5T$ymZb};o)}V?&>eiiN z#Cr9M^Xi*+Sd-9_{M6abQ2p4_2}>EE8T&Rr;LZfiH^=6s?y8{Dmw16dr|l0gx|B40 zYk2CLkSwDd$HJs{cdNDgsH<uKKu<3YXH2emPR3C z1sJ4?0MsJzOl<-^j~{VH$9ZGRs|`Bc{28{LZQv4h`l5c7l7qDCxciAww+j09G;jET zIs;Epk)i{9_H=SGMs>4;mp%11{lc;Kvg{}J4w?%wodHSquHiacpBC0}h@iy}FbX(r zA2IMVXwlSpdPM3#F8@c+BH)Z`*+5=w&@Q81L=M#{Hpc%~_YPbIUt?Z)+XY@Ww!ic%+ z$RG_UTt2IUcI}DAiloq}+LVKUB|xIFBv5m>8=VJ`C^Q?-$^ODx+Vbr{+>>LfNtaS@ zzS}X_T|NHOxuHwzzr5QCXx8A=KxLZIdl@pN#@q0`GIPxP3Up(QuT5Z2&b(SkUwLJ$ zrnfLZ_5Ci42wF@Gyj)oJez(<9TYuw>%f+ql_i+54`FOemtIUW$nuU`bZ#w4hS1hG) z74*c`={r6K)_@4^GmnoJu>sI?)V_M@zN;AlDl9nTIk5YD?eth+yM_x_pVFAaNsh+W z_mO6;^FuTlB6bOe7{HYo`eMR(mABK2T4o0MnKRv6&d}FZK)x~k#&dvn;wW$C5>;h= z>9jNS%!=!!HKR0I!nkH|BV~>Z$9AETQH|h@$(lacjB66gOb%RO-JwkiSKtDaCK|%h zD0?n!nM%HYGhF~}r4GOrbSQJ$!z0ryF_LqPE6o5uh#d

    (66~vAz$@#Y@CYWLl=k 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/user-manual/blocks/block_left_plus_icon.png b/docs/source/_static/user-manual/blocks/block_left_plus_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb7c63d93c3b0683ecac135085024446a5994dd GIT binary patch literal 5748 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/user-manual/blocks/blocks_type_menu.png b/docs/source/_static/user-manual/blocks/blocks_type_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd3a579162f7ac06765227bea98e4480bcfa69f GIT binary patch literal 34286 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 beMT7CE+HXQCo6xs$F!uVoJfh#=WqWBtu~%H literal 0 HcmV?d00001 diff --git a/docs/source/index.md b/docs/source/index.md index ec1b7e40e6..222455dae2 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -48,5 +48,6 @@ backend/index deploying/index upgrade-guide/index developer-guidelines/index +user-manual/index release-notes/index ``` diff --git a/docs/source/user-manual/blocks.md b/docs/source/user-manual/blocks.md new file mode 100644 index 0000000000..4ca9104a80 --- /dev/null +++ b/docs/source/user-manual/blocks.md @@ -0,0 +1,85 @@ +--- +myst: + html_meta: + "description": "User manual for how to edit blocks in Volto, the Plone 6 frontend." + "property=og:description": "User manual for how to edit blocks in Volto, the Plone 6 frontend." + "property=og:title": "How to edit content using Volto blocks" + "keywords": "Volto, Plone, frontend, React, User manual, edit blocks" +--- + +# Edit content using blocks + +```{todo} +This page needs content +``` + +Volto features the [Pastanaga UI](https://github.com/plone/pastanaga), allowing you to visually compose a page using blocks. +The blocks editor allows you to add, modify, reorder, and delete blocks given your requirements. +Blocks provide the user the ability to display content in a specific way, although they can also define behavior and have specific features. + +## Choose a pre-built block type + +```{todo} +Add description of a Volto block. +``` + +Volto offers several default block types out of the box. +You can access and choose a block type to add to your content type when you have an empty block in it. + +To create an empty block after an existing block, click in the block, then hit the {kbd}`Enter` key. +A new empty block appears. + +```{image} ../_static/user-manual/blocks/add_new_block.gif +:alt: add new block +``` + +```{note} +There is a new experimental feature that places a `+` below every block in a content type. + +See https://github.com/plone/volto/pull/3815 for details of the feature and how to enable it. +``` + +Now with your empty block available, you can select its type in one of two ways. +1. Click the `+` button on the left-hand side of the empty block. + ```{image} ../_static/user-manual/blocks/block_left_plus_icon.png + :alt: Plus button + ``` +2. Type `/` inside empty block to open the blocks menu. + ```{image} ../_static/user-manual/blocks/blocks_type_menu.png + :alt: Types of blocks menu + ``` + + +(user-manual-description-block-label)= + +## Description Block + +A description block provides a blank textarea to write plain text. + +(user-manual-grid-block-label)= + +## Grid Block + +(user-manual-html-block-label)= + +## HTML Block + +(user-manual-hero-block-label)= + +## Hero Block + +(user-manual-image-block-label)= + +## Image Block + +(user-manual-images-grid-block-label)= + +## Images grid Block + +(user-manual-listing-block-label)= + +## Listing Block + +(user-manual-maps-block-label)= + +## Maps Block diff --git a/docs/source/user-manual/index.md b/docs/source/user-manual/index.md new file mode 100644 index 0000000000..02f386abf1 --- /dev/null +++ b/docs/source/user-manual/index.md @@ -0,0 +1,33 @@ +--- +myst: + html_meta: + "description": "Volto User Manual for Plone 6" + "property=og:description": "Volto User Manual for Plone 6" + "property=og:title": "Frontend" + "keywords": "Volto, Plone, frontend, React, User manual" +--- + + +(user-manual-label)= + +# User Manual + +This is the user manual for Volto, the Plone 6 frontend. +```{todo} +The Plone 5.2 documentation for Classic UI is well structured, and should be emulated as the model for the Volto User Manual. + +- [Working with Content](https://docs.plone.org/working-with-content/index.html) + +Additional sources of content may be used to fill out the Volto User Manual. +Note that the audience for these sources may be a Developer, not an Editor, and therefore might need to be retargeted. + +- [Volto Hands-On](https://training.plone.org/voltohandson/index.html) +- [Plone 6 Demo](https://6.demo.plone.org/) for screen shots and videos. + +``` +```{toctree} +:maxdepth: 1 + +blocks + +``` diff --git a/news/3827.documentation b/news/3827.documentation new file mode 100644 index 0000000000..fdc684a91e --- /dev/null +++ b/news/3827.documentation @@ -0,0 +1 @@ +Add content for user-manual of Volto, Plone 6 frontend. [@MAX-786] \ No newline at end of file From b21cfa9ff38cb83234eebb417bbba4d1c72ac55d Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 12 Dec 2022 11:28:35 +0200 Subject: [PATCH 111/326] Don't crash on unknown blocks (#4070) Co-authored-by: David Glick --- cypress/support/commands.js | 17 +++++++++-------- cypress/tests/core/blocks/blocks.js | 15 +++++++++++++++ news/4070.bugfix | 1 + .../manage/Blocks/Block/DefaultView.jsx | 2 +- 4 files changed, 26 insertions(+), 9 deletions(-) create mode 100644 news/4070.bugfix diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 2f5908903b..9021feacfe 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -49,6 +49,7 @@ Cypress.Commands.add( path = '', allow_discussion = false, transition = '', + bodyModifier = (body) => body, }) => { let api_url, auth; if (Cypress.env('API') === 'guillotina') { @@ -69,7 +70,7 @@ Cypress.Commands.add( Accept: 'application/json', }, auth: auth, - body: { + body: bodyModifier({ '@type': contentType, id: contentId, title: contentTitle, @@ -80,7 +81,7 @@ Cypress.Commands.add( 'content-type': 'text/plain', }, allow_discussion: allow_discussion, - }, + }), }); } if (contentType === 'Image') { @@ -91,7 +92,7 @@ Cypress.Commands.add( Accept: 'application/json', }, auth: auth, - body: { + body: bodyModifier({ '@type': contentType, id: contentId, title: contentTitle, @@ -102,7 +103,7 @@ Cypress.Commands.add( filename: 'image.png', 'content-type': 'image/png', }, - }, + }), }); } if ( @@ -116,7 +117,7 @@ Cypress.Commands.add( Accept: 'application/json', }, auth: auth, - body: { + body: bodyModifier({ '@type': contentType, id: contentId, title: contentTitle, @@ -131,7 +132,7 @@ Cypress.Commands.add( ], }, allow_discussion: allow_discussion, - }, + }), }) .then(() => { if (transition) { @@ -151,12 +152,12 @@ Cypress.Commands.add( Accept: 'application/json', }, auth: auth, - body: { + body: bodyModifier({ '@type': contentType, id: contentId, title: contentTitle, allow_discussion: allow_discussion, - }, + }), }) .then(() => { if (transition) { diff --git a/cypress/tests/core/blocks/blocks.js b/cypress/tests/core/blocks/blocks.js index 7e0912118d..5175c29d80 100644 --- a/cypress/tests/core/blocks/blocks.js +++ b/cypress/tests/core/blocks/blocks.js @@ -235,4 +235,19 @@ describe('Blocks Tests', () => { // 'header-two', // ); // }); + + it('Handles unknown blocks', () => { + cy.createContent({ + contentType: 'Document', + contentId: 'test-doc', + contentTitle: 'my test document', + bodyModifier(body) { + body.blocks['abc'] = { '@type': 'missing' }; + body.blocks_layout.items.push('abc'); + return body; + }, + }); + cy.visit('/test-doc'); + cy.get('#page-document div').should('have.text', 'Unknown Block missing'); + }); }); diff --git a/news/4070.bugfix b/news/4070.bugfix new file mode 100644 index 0000000000..0d2b23feba --- /dev/null +++ b/news/4070.bugfix @@ -0,0 +1 @@ +Don't crash the view page when dealing with unknown blocks @tiberiuichim diff --git a/src/components/manage/Blocks/Block/DefaultView.jsx b/src/components/manage/Blocks/Block/DefaultView.jsx index ceaa3911aa..c898e3676a 100644 --- a/src/components/manage/Blocks/Block/DefaultView.jsx +++ b/src/components/manage/Blocks/Block/DefaultView.jsx @@ -30,7 +30,7 @@ const DefaultBlockView = (props) => { typeof blockSchema === 'function' ? blockSchema({ ...props, intl }) : blockSchema; - const fieldsets = schema.fieldsets || []; + const fieldsets = schema?.fieldsets || []; return schema ? ( From a733442bf145f77c096ef2b8f5238bc86028ca76 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 12 Dec 2022 11:29:23 +0200 Subject: [PATCH 112/326] Bump version for plone-backend (#4071) Co-authored-by: David Glick --- Makefile | 2 +- api/buildout.cfg | 2 +- news/4071.bugfix | 1 + packages/generator-volto/generators/app/templates/Makefile | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 news/4071.bugfix diff --git a/Makefile b/Makefile index ef0a7f914a..ccc3c68540 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ MAKEFLAGS+=--no-builtin-rules # Project settings INSTANCE_PORT=8080 -DOCKER_IMAGE=plone/plone-backend:6.0.0rc1 +DOCKER_IMAGE=plone/plone-backend:6.0.0rc2 KGS= TESTING_ADDONS=plone.app.robotframework==2.0.0b2 plone.app.testing==7.0.0b2 NODEBIN = ./node_modules/.bin diff --git a/api/buildout.cfg b/api/buildout.cfg index ff3e424784..09a9903535 100644 --- a/api/buildout.cfg +++ b/api/buildout.cfg @@ -1,7 +1,7 @@ [buildout] index = https://pypi.org/simple/ extends = - http://dist.plone.org/release/6.0.0rc1/versions.cfg + http://dist.plone.org/release/6.0.0rc2/versions.cfg version-constraints.cfg versions.cfg parts = instance plonesite site-packages test robot-server diff --git a/news/4071.bugfix b/news/4071.bugfix new file mode 100644 index 0000000000..5ab6292c88 --- /dev/null +++ b/news/4071.bugfix @@ -0,0 +1 @@ +Bump version for plone-backend version used in Makefile @tiberiuichim diff --git a/packages/generator-volto/generators/app/templates/Makefile b/packages/generator-volto/generators/app/templates/Makefile index 9b7b434aba..ad1f4f6a34 100644 --- a/packages/generator-volto/generators/app/templates/Makefile +++ b/packages/generator-volto/generators/app/templates/Makefile @@ -8,8 +8,8 @@ SHELL:=bash MAKEFLAGS+=--warn-undefined-variables MAKEFLAGS+=--no-builtin-rules -# Update the versions depending on your project requirements | Last Updated 2022-11-24 -DOCKER_IMAGE=plone/plone-backend:6.0.0rc1 +# Update the versions depending on your project requirements | Last Updated 2022-12-09 +DOCKER_IMAGE=plone/plone-backend:6.0.0rc2 KGS= TESTING_ADDONS=plone.app.robotframework==2.0.0b2 plone.app.testing==7.0.0b2 NODEBIN = ./node_modules/.bin From 3b0f71f2c6bbc726057f16daec9ea4b5f2538562 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 12 Dec 2022 11:30:04 +0200 Subject: [PATCH 113/326] Allow addons to provide a eslint.extend.js (#4072) --- addon-registry.js | 22 ++++++++++--- docs/source/addons/index.md | 32 +++++++++++++++++++ news/4072.feature | 1 + .../generators/app/templates/.eslintrc.js | 11 ++++++- 4 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 news/4072.feature diff --git a/addon-registry.js b/addon-registry.js index 3dfea21f45..666c0bcd04 100644 --- a/addon-registry.js +++ b/addon-registry.js @@ -148,7 +148,7 @@ class AddonConfigurationRegistry { }, ); - this.initRazzleExtenders(); + this.initAddonExtenders(); } /** @@ -310,18 +310,26 @@ class AddonConfigurationRegistry { } /** - * Allow addons to provide razzle.config extenders. These extenders - * modules (named razzle.extend.js) need to provide two functions: + * Allow addons to provide various extenders. + * + * The razzle.extend.js modules (named razzle.extend.js) needs to provide + * two functions: * `plugins(defaultPlugins) => plugins` and * `modify(...) => config` + * + * The eslint.extend.js */ - initRazzleExtenders() { + initAddonExtenders() { this.getAddons().forEach((addon) => { const base = path.dirname(addon.packageJson); const razzlePath = path.resolve(`${base}/razzle.extend.js`); if (fs.existsSync(razzlePath)) { addon.razzleExtender = razzlePath; } + const eslintPath = path.resolve(`${base}/eslint.extend.js`); + if (fs.existsSync(eslintPath)) { + addon.eslintExtender = eslintPath; + } }); } @@ -340,6 +348,12 @@ class AddonConfigurationRegistry { .filter((e) => e); } + getEslintExtenders() { + return this.getAddons() + .map((o) => o.eslintExtender) + .filter((e) => e); + } + /** * Returns a mapping name:diskpath to be uses in webpack's resolve aliases */ diff --git a/docs/source/addons/index.md b/docs/source/addons/index.md index de1b615520..49f1dd3af6 100644 --- a/docs/source/addons/index.md +++ b/docs/source/addons/index.md @@ -400,6 +400,38 @@ module.exports = { }; ``` +## Extending Eslint configuration from an addon + +Starting with Volto v16.4.0, you can also customize the Eslint configuration +from an addon. You should provide a `eslint.extend.js` file in your +addon root folder, which exports a `modify(defaultConfig)` function. For +example, to host some code outside the regular `src/` folder of your addon, +this `eslint.extend.js` file is needed: + +``` +const path = require('path'); + +module.exports = { + modify(defaultConfig) { + const aliasMap = defaultConfig.settings['import/resolver'].alias.map; + const addonPath = aliasMap.find( + ([name]) => name === '@plone-collective/some-volto-addon', + )[1]; + + const extraPath = path.resolve(`${addonPath}/../extra`); + aliasMap.push(['@plone-collective/extra', extraPath]); + + return defaultConfig; + }, +}; +``` + +This would allow the `@plone-collective/some-volto-addon` to host some code +outside of its normal `src/` folder, let's say in the `extra` folder, and that +code would be available under the `@plone-collective/extra` name. Note: this is +taking care only of the Eslint integration. For proper language support, you'll +still need to do it in the `razzle.extend.js` of your addon. + ## Addon dependencies Sometimes your addon depends on another addon. You can declare addon dependency diff --git a/news/4072.feature b/news/4072.feature new file mode 100644 index 0000000000..09ab0ed4a0 --- /dev/null +++ b/news/4072.feature @@ -0,0 +1 @@ +Allow addons to provide an `eslint.extend.js` file that customizez eslint configuration @tiberiuichim diff --git a/packages/generator-volto/generators/app/templates/.eslintrc.js b/packages/generator-volto/generators/app/templates/.eslintrc.js index f8f1e7ec8d..6e957091f2 100644 --- a/packages/generator-volto/generators/app/templates/.eslintrc.js +++ b/packages/generator-volto/generators/app/templates/.eslintrc.js @@ -27,7 +27,9 @@ const addonAliases = Object.keys(reg.packages).map((o) => [ reg.packages[o].modulePath, ]); -module.exports = { +const addonExtenders = reg.getEslintExtenders().map((m) => require(m)); + +const defaultConfig = { extends: `${voltoPath}/.eslintrc`, settings: { 'import/resolver': { @@ -48,3 +50,10 @@ module.exports = { }, }, }; + +const config = addonExtenders.reduce( + (acc, extender) => extender.modify(acc), + defaultConfig, +); + +module.exports = config; From a07c92f24616110bd5dbe30e2beea0e1095af7c1 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 12 Dec 2022 11:31:26 +0200 Subject: [PATCH 114/326] Generator: Allow passing a volto branch as volto version (#4073) Co-authored-by: David Glick Co-authored-by: Steve Piercy --- docs/source/getting-started/install.md | 16 ++------- docs/source/recipes/creating-project.md | 17 ++++++++-- packages/generator-volto/README.md | 6 ++++ packages/generator-volto/__tests__/app.js | 33 +++++++++++++++++++ .../generator-volto/generators/app/utils.js | 8 ++++- packages/generator-volto/news/4073.feature | 1 + 6 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 packages/generator-volto/news/4073.feature diff --git a/docs/source/getting-started/install.md b/docs/source/getting-started/install.md index 97bc9c0ca2..0497303114 100644 --- a/docs/source/getting-started/install.md +++ b/docs/source/getting-started/install.md @@ -221,23 +221,13 @@ You may choose to install the canary version, which is the latest alpha release, ```bash npm install -g yo @plone/generator-volto - # install latest stable release yo @plone/volto - # or install latest alpha release - yo @plone/volto --canary ``` -2. Answer to the prompted questions and provide the name of the new app (folder) to be created. For the sake of this documentation, provide `myvoltoproject` as project name then. +See the [Creating a project](../recipes/creating-project) page for more +advanced options that can be passed to the generator. - ````{note} - You can run the generator with parameters to tailor your requirements. - - ```bash - yo @plone/volto --help - ``` - - or take a look at the [README](https://github.com/plone/volto/blob/master/packages/generator-volto/README.md) for more information. - ```` +2. Answer the questions when prompted, and provide the name of the new app (folder) to be created. For the sake of this documentation, provide `myvoltoproject` as the project name. 3. Change directory to the newly created folder `myvoltoapp` (or the one you've chosen): ```bash diff --git a/docs/source/recipes/creating-project.md b/docs/source/recipes/creating-project.md index 80ea0876f1..c577cedd35 100644 --- a/docs/source/recipes/creating-project.md +++ b/docs/source/recipes/creating-project.md @@ -9,16 +9,27 @@ myst: # Creating a new Volto project -For using Volto for a project (i.e. use Volto as a library), You should use Volto's project generator `@plone/generator-volto`. It's a boilerplate generator based in Yeoman that will provide you with the basic files and folder structure to bootstrap a Volto site. In addition to bootstrapping standalone Volto projects, it can also bootstrap Volto addons. +For using Volto for a project—in other words, use Volto as a library—you should use Volto's project generator `@plone/generator-volto`. +It's a boilerplate generator based in Yeoman that will provide you with the basic files and folder structure to bootstrap a Volto site. +In addition to bootstrapping stand-alone Volto projects, it can also bootstrap Volto add-ons. 1. Open a terminal and execute: - ```shell + ```bash npm install -g yo @plone/generator-volto + # Install the latest and stable release of Volto with the following command yo @plone/volto + # or you can install the "canary" release, including any alpha release + yo @plone/volto --canary + # or you can install any specific released version + yo @plone/volto --volto=15.0.0 + # you can even pass a GitHub repo and specific branch + yo @plone/volto --volto=plone/volto#16.0.0 + # you can bootstrap with add-ons + yo @plone/volto --addon=volto-form-block ``` -2. Answer to the prompted questions and provide the name of the new app (folder) to be created. For the sake of this documentation, provide `myvoltoproject` as project name then. +2. Answer the questions when prompted, and provide the name of the new app (folder) to be created. For the sake of this documentation, provide `myvoltoproject` as the project name then. ````{note} You can run the generator with parameters to tailor your requirements. diff --git a/packages/generator-volto/README.md b/packages/generator-volto/README.md index 32631e4c54..662a4df314 100644 --- a/packages/generator-volto/README.md +++ b/packages/generator-volto/README.md @@ -64,6 +64,12 @@ You can provide an specific Volto version like: yo @plone/volto --volto=12.0.0-alpha.0 ``` +You can also pass a Volto branch or tag: + +```shell +yo @plone/volto --volto=plone/volto#16.3.0 +``` + You can force to use the latest canary (alpha) Volto version like: ```bash diff --git a/packages/generator-volto/__tests__/app.js b/packages/generator-volto/__tests__/app.js index de33dc9b6b..f533627c97 100644 --- a/packages/generator-volto/__tests__/app.js +++ b/packages/generator-volto/__tests__/app.js @@ -71,3 +71,36 @@ describe('generator-create-volto-app:app with canary option', () => { ).toBe(true); }); }); + +describe('generator-create-volto-app:app with volto from Github branch', () => { + beforeAll(() => { + return helpers + .run(path.join(__dirname, '../generators/app')) + .inTmpDir(function (dir) { + tmpDir = dir; + }) + .withPrompts({ + projectName: 'test-volto', + useAddons: false, + }) + .withOptions({ + volto: 'plone/volto#16.3.0', + }); + }); + + it('creates files', () => { + assert.file([ + 'test-volto/package.json', + 'test-volto/yarn.lock', + 'test-volto/.gitignore', + ]); + }); + + it('Volto is at custom version', () => { + const packageJSON = JSON.parse( + fs.readFileSync(path.join(tmpDir, 'test-volto/package.json'), 'utf8'), + ); + + expect(packageJSON.dependencies['@plone/volto']).toBe('plone/volto#16.3.0'); + }); +}); diff --git a/packages/generator-volto/generators/app/utils.js b/packages/generator-volto/generators/app/utils.js index fa007014e3..afd0855142 100644 --- a/packages/generator-volto/generators/app/utils.js +++ b/packages/generator-volto/generators/app/utils.js @@ -4,7 +4,13 @@ const https = require('https'); * Retrieves Volto's yarn.lock directly from github */ async function getVoltoYarnLock(version) { - const url = `https://raw.githubusercontent.com/plone/volto/${version}/yarn.lock`; + let ghPath = 'plone/volto'; + + if (version.indexOf('#') > -1) { + [ghPath, version] = version.split('#'); + } + + const url = `https://raw.githubusercontent.com/${ghPath}/${version}/yarn.lock`; return new Promise((resolve, reject) => { https .get(url, (resp) => { diff --git a/packages/generator-volto/news/4073.feature b/packages/generator-volto/news/4073.feature new file mode 100644 index 0000000000..a4664d09ba --- /dev/null +++ b/packages/generator-volto/news/4073.feature @@ -0,0 +1 @@ +Allow passing a Github branch or tag as Volto version (ex: `--volto=plone/volto#16.3.0`) @tiberiuichim From aa53b68e46e6caec2b6ca028ecdda91395a3e88d Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 12 Dec 2022 11:35:50 +0200 Subject: [PATCH 115/326] Generator --canary option always gets latest Volto, including any alpha or RC (#4075) Co-authored-by: Alin Voinea --- .../__tests__/{addon.js => test_addon.js} | 0 .../__tests__/{app.js => test_app.js} | 33 +++++++++++++++---- .../generator-volto/generators/app/utils.js | 11 +++---- packages/generator-volto/news/4074.bugfix | 1 + packages/generator-volto/package.json | 1 + packages/generator-volto/yarn.lock | 12 +++++++ 6 files changed, 45 insertions(+), 13 deletions(-) rename packages/generator-volto/__tests__/{addon.js => test_addon.js} (100%) rename packages/generator-volto/__tests__/{app.js => test_app.js} (78%) create mode 100644 packages/generator-volto/news/4074.bugfix diff --git a/packages/generator-volto/__tests__/addon.js b/packages/generator-volto/__tests__/test_addon.js similarity index 100% rename from packages/generator-volto/__tests__/addon.js rename to packages/generator-volto/__tests__/test_addon.js diff --git a/packages/generator-volto/__tests__/app.js b/packages/generator-volto/__tests__/test_app.js similarity index 78% rename from packages/generator-volto/__tests__/app.js rename to packages/generator-volto/__tests__/test_app.js index f533627c97..cb99318356 100644 --- a/packages/generator-volto/__tests__/app.js +++ b/packages/generator-volto/__tests__/test_app.js @@ -5,6 +5,30 @@ const fs = require('fs-extra'); let tmpDir; +jest.mock('https', () => ({ + methodToMock: {}, +})); +const httpsMock = require('https'); + +const Stream = require('stream'); + +httpsMock.get = jest.fn().mockImplementation((url, headers, cb) => { + let streamStream = new Stream(); + cb(streamStream); + + const json = JSON.stringify({ + name: '@plone/volto', + 'dist-tags': { + latest: '16.3.0', + alpha: '16.0.0-alpha.53', + rc: '16.0.0-rc.3', + }, + }); + + streamStream.emit('data', json); + streamStream.emit('end'); +}); + describe('generator-create-volto-app:app', () => { beforeAll(() => { return helpers @@ -59,16 +83,11 @@ describe('generator-create-volto-app:app with canary option', () => { ]); }); - it('canary option gets alpha version', () => { + it('canary option gets latest Volto version, including alphas', () => { const packageJSON = JSON.parse( fs.readFileSync(path.join(tmpDir, 'test-volto/package.json'), 'utf8'), ); - - expect( - packageJSON.dependencies['@plone/volto'].includes(['rc']) || - packageJSON.dependencies['@plone/volto'].includes(['beta']) || - packageJSON.dependencies['@plone/volto'].includes(['alpha']), - ).toBe(true); + expect(packageJSON.dependencies['@plone/volto']).toBe('16.3.0'); }); }); diff --git a/packages/generator-volto/generators/app/utils.js b/packages/generator-volto/generators/app/utils.js index afd0855142..dd130de45f 100644 --- a/packages/generator-volto/generators/app/utils.js +++ b/packages/generator-volto/generators/app/utils.js @@ -1,4 +1,5 @@ const https = require('https'); +const rcompare = require('semver/functions/rcompare'); /* * Retrieves Volto's yarn.lock directly from github @@ -13,7 +14,7 @@ async function getVoltoYarnLock(version) { const url = `https://raw.githubusercontent.com/${ghPath}/${version}/yarn.lock`; return new Promise((resolve, reject) => { https - .get(url, (resp) => { + .get(url, {}, (resp) => { let data = ''; resp.on('data', (chunk) => { data += chunk; @@ -75,11 +76,9 @@ async function getLatestCanaryVoltoVersion() { }); resp.on('end', () => { const res = JSON.parse(data.join('')); - resolve( - res['dist-tags'].rc || - res['dist-tags'].beta || - res['dist-tags'].alpha, - ); + const versions = Object.values(res['dist-tags']); + const latest = versions.sort(rcompare)[0]; + resolve(latest); }); }, ) diff --git a/packages/generator-volto/news/4074.bugfix b/packages/generator-volto/news/4074.bugfix new file mode 100644 index 0000000000..986a2d34ed --- /dev/null +++ b/packages/generator-volto/news/4074.bugfix @@ -0,0 +1 @@ +Use semver to identify latest package when using the `--canary` flag @tiberiuichim @avoinea diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index d3a1d91cf8..093f25510c 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -112,6 +112,7 @@ "mz": "2.6.0", "ora": "1.2.0", "promise": "7.1.1", + "semver": "7.3.8", "update-notifier": "^5.0.1", "yeoman-generator": "^4.12.0" }, diff --git a/packages/generator-volto/yarn.lock b/packages/generator-volto/yarn.lock index 893102754b..0c880d00f0 100644 --- a/packages/generator-volto/yarn.lock +++ b/packages/generator-volto/yarn.lock @@ -688,6 +688,7 @@ __metadata: prettier: 2.0.5 promise: 7.1.1 release-it: ^14.9.0 + semver: 7.3.8 update-notifier: ^5.0.1 yeoman-assert: ^3.1.0 yeoman-generator: ^4.12.0 @@ -8105,6 +8106,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:7.3.8": + version: 7.3.8 + resolution: "semver@npm:7.3.8" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 + languageName: node + linkType: hard + "semver@npm:^6.0.0, semver@npm:^6.1.2, semver@npm:^6.2.0, semver@npm:^6.3.0": version: 6.3.0 resolution: "semver@npm:6.3.0" From fd81204e0c58933b2b3b4f98761c1b5507929c55 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 12 Dec 2022 17:39:53 +0800 Subject: [PATCH 116/326] add Chinese po file (#4009) --- locales/zh_CN/LC_MESSAGES/volto.po | 4474 ++++++++++++++++++++++++++++ news/4009.bugfix | 0 2 files changed, 4474 insertions(+) create mode 100644 locales/zh_CN/LC_MESSAGES/volto.po create mode 100644 news/4009.bugfix diff --git a/locales/zh_CN/LC_MESSAGES/volto.po b/locales/zh_CN/LC_MESSAGES/volto.po new file mode 100644 index 0000000000..499d559cbd --- /dev/null +++ b/locales/zh_CN/LC_MESSAGES/volto.po @@ -0,0 +1,4474 @@ +msgid "" +msgstr "" +"Project-Id-Version: Plone\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Language-Code: zh_CN\n" +"Language-Name: Chinese\n" +"Preferred-Encodings: utf-8\n" +"X-Is-Fallback-For: zh-cn\n" +"X-Generator: Poedit 3.2\n" + +# defaultMessage:

    Add some HTML here

    +#: components/manage/Blocks/HTML/Edit +msgid "

    Add some HTML here

    " +msgstr "在此处添加一些HTML" + +# defaultMessage: Account Registration Completed +#: components/theme/Register/Register +msgid "Account Registration Completed" +msgstr "账号注册完成" + +# defaultMessage: Account activation completed +#: components/theme/PasswordReset/PasswordReset +msgid "Account activation completed" +msgstr "账号激活完成" + +# defaultMessage: Action +#: components/manage/Controlpanels/ModerateComments +msgid "Action" +msgstr "操作" + +# defaultMessage: Action changed +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Action changed" +msgstr "操作改变" + +# defaultMessage: Action: +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Action: " +msgstr "操作:" + +# defaultMessage: Actions +#: components/manage/Actions/Actions components/manage/Contents/Contents +#: components/manage/Controlpanels/ContentTypes +#: components/manage/Controlpanels/Groups/GroupsControlpanel +#: components/manage/Controlpanels/Rules/Rules +#: components/manage/Controlpanels/Users/UsersControlpanel +msgid "Actions" +msgstr "操作" + +# defaultMessage: Activate and deactivate add-ons in the lists below. +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Activate and deactivate" +msgstr "启用或者禁用" + +# defaultMessage: Active +#: components/manage/Rules/Rules +msgid "Active" +msgstr "已启用" + +# defaultMessage: Active content rules in this Page +#: components/manage/Rules/Rules +msgid "Active content rules in this Page" +msgstr "在此页面中活动的内容规则" + +# defaultMessage: Add +#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases +#: components/manage/Controlpanels/ContentTypes +#: components/manage/Controlpanels/Rules/ConfigureRule +#: components/manage/Rules/Rules components/manage/Toolbar/Toolbar +#: components/manage/Widgets/SchemaWidget helpers/MessageLabels/MessageLabels +msgid "Add" +msgstr "添加" + +# defaultMessage: Add +#: components/manage/Widgets/ObjectListWidget +msgid "Add (object list)" +msgstr "添加(对象列表)" + +# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Add Addons" +msgstr "添加附件" + +# defaultMessage: Add Content… +#: components/manage/Toolbar/Types +msgid "Add Content" +msgstr "添加内容" + +# defaultMessage: Add Content Rule +#: components/manage/Controlpanels/Rules/AddRule +msgid "Add Content Rule" +msgstr "添加内容规则" + +# defaultMessage: Add Rule +#: components/manage/Controlpanels/Rules/AddRule +msgid "Add Rule" +msgstr "添加规则" + +# defaultMessage: Add Translation… +#: components/manage/Toolbar/Types +msgid "Add Translation…" +msgstr "添加翻译" + +# defaultMessage: Add User +#: helpers/MessageLabels/MessageLabels +msgid "Add User" +msgstr "添加用户" + +# defaultMessage: Add a description… +#: components/manage/Blocks/Description/Edit +msgid "Add a description…" +msgstr "添加描述" + +# defaultMessage: Add a new alternative url +#: components/manage/Aliases/Aliases +msgid "Add a new alternative url" +msgstr "添加新的替代 URL" + +# defaultMessage: Action added +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Add action" +msgstr "添加操作" + +# defaultMessage: Add block +#: components/manage/BlockChooser/BlockChooserButton +msgid "Add block" +msgstr "添加block" + +# defaultMessage: Add block… +#: helpers/MessageLabels/MessageLabels +msgid "Add block…" +msgstr "添加块..." + +# defaultMessage: Condition added +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Add condition" +msgstr "添加条件" + +# defaultMessage: Add content rule +#: components/manage/Controlpanels/Rules/Rules +msgid "Add content rule" +msgstr "添加内容规则" + +# defaultMessage: Add criteria +#: components/manage/Widgets/QueryWidget +msgid "Add criteria" +msgstr "添加标准" + +# defaultMessage: Add date +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Add date" +msgstr "添加日期" + +# defaultMessage: Add field +#: components/manage/Widgets/SchemaWidget +msgid "Add field" +msgstr "添加字段" + +# defaultMessage: Add fieldset +#: components/manage/Widgets/SchemaWidget +msgid "Add fieldset" +msgstr "添加字段集" + +# defaultMessage: Add group +#: helpers/MessageLabels/MessageLabels +msgid "Add group" +msgstr "添加组" + +# defaultMessage: Add new content type +#: components/manage/Controlpanels/ContentTypes +msgid "Add new content type" +msgstr "添加新的内容类型" + +# defaultMessage: Add new group +#: helpers/MessageLabels/MessageLabels +msgid "Add new group" +msgstr "添加新组" + +# defaultMessage: Add new user +#: helpers/MessageLabels/MessageLabels +msgid "Add new user" +msgstr "添加新用户" + +# defaultMessage: Add to Groups +#: helpers/MessageLabels/MessageLabels +msgid "Add to Groups" +msgstr "添加到组" + +# defaultMessage: Add users to group +#: helpers/MessageLabels/MessageLabels +msgid "Add users to group" +msgstr "添加用户到组" + +# defaultMessage: Add term +#: components/manage/Widgets/VocabularyTermsWidget +msgid "Add vocabulary term" +msgstr "添加词汇" + +# defaultMessage: Add {type} +#: components/manage/Add/Add +msgid "Add {type}" +msgstr "添加{type}" + +# defaultMessage: Add-Ons +#: components/manage/Controlpanels/Controlpanels +msgid "Add-Ons" +msgstr "附加组件" + +# defaultMessage: Add-on Configuration +#: components/manage/Controlpanels/Controlpanels +msgid "Add-on Configuration" +msgstr "附加组件配置" + +# defaultMessage: Add-ons +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Add-ons" +msgstr "附加组件" + +# defaultMessage: Add-ons Settings +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Add-ons Settings" +msgstr "附加组件设置" + +# defaultMessage: Added +#: components/manage/Rules/Rules +msgid "Added" +msgstr "已添加" + +# defaultMessage: Additional date +#: components/manage/Widgets/RecurrenceWidget/Occurences +msgid "Additional date" +msgstr "附加日期" + +# defaultMessage: Addon could not be installed +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Addon could not be installed" +msgstr "无法安装附件" + +# defaultMessage: Addon could not be uninstalled +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Addon could not be uninstalled" +msgstr "无法卸载附件" + +# defaultMessage: Addon could not be upgraded +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Addon could not be upgraded" +msgstr "无法升级附件" + +# defaultMessage: Addon installed succesfuly +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Addon installed succesfuly" +msgstr "附件安装成功" + +# defaultMessage: Addon uninstalled succesfuly +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Addon uninstalled succesfuly" +msgstr "附件卸载成功" + +# defaultMessage: Addon upgraded succesfuly +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Addon upgraded succesfuly" +msgstr "附件升级成功" + +# defaultMessage: Album view +#: config/Views +msgid "Album view" +msgstr "相册视图" + +# defaultMessage: Alias +#: components/manage/Controlpanels/Aliases +msgid "Alias" +msgstr "别名" + +# defaultMessage: Alias has been added +#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases +msgid "Alias has been added" +msgstr "别名已添加" + +# defaultMessage: Alignment +#: components/manage/Blocks/Image/schema +#: components/manage/Blocks/LeadImage/LeadImageSidebar +#: components/manage/Blocks/Maps/schema components/manage/Blocks/Video/schema +msgid "Alignment" +msgstr "对齐" + +# defaultMessage: All +#: components/manage/Contents/Contents +msgid "All" +msgstr "所有" + +# defaultMessage: All content +#: config/Views +msgid "All content" +msgstr "所有内容" + +# defaultMessage: All existing alternative urls for this site +#: components/manage/Controlpanels/Aliases +msgid "All existing alternative urls for this site" +msgstr "此网站所有的替代 urls" + +# defaultMessage: Alphabetically +#: components/theme/Search/Search +msgid "Alphabetically" +msgstr "按字母表顺序" + +# defaultMessage: Alt text +#: components/manage/Blocks/Image/schema +#: components/manage/Blocks/LeadImage/LeadImageSidebar +#: components/manage/Blocks/Maps/schema +msgid "Alt text" +msgstr "替换文本" + +# defaultMessage: Leave empty if the image is purely decorative. +#: components/manage/Blocks/Image/schema +msgid "Alt text hint" +msgstr "替换文本提示" + +# defaultMessage: Describe the purpose of the image. +#: components/manage/Blocks/Image/schema +msgid "Alt text hint link text" +msgstr "替换文本提示链接文本" + +# defaultMessage: Alternative url path (Required) +#: components/manage/Controlpanels/Aliases +msgid "Alternative url path (Required)" +msgstr "替代 URL 路径(必选)" + +# defaultMessage: Alternative url path must start with a slash. +#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases +msgid "Alternative url path must start with a slash." +msgstr "替代 URL 路径必须以斜杠开头。" + +# defaultMessage: Alternative url path → target url path (date and time of creation, manually created yes/no) +#: components/manage/Controlpanels/Aliases +msgid "Alternative url path → target url path (date and time of creation, manually created yes/no)" +msgstr "替代 URL 路径 → 目标 URL 路径(创建的日期和时间,手动创建 yes/no)" + +# defaultMessage: Applied to subfolders +#: components/manage/Rules/Rules +msgid "Applied to subfolders" +msgstr "应用于子文件夹" + +# defaultMessage: Applies to subfolders? +#: components/manage/Rules/Rules +msgid "Applies to subfolders?" +msgstr "是否应用于子文件夹?" + +# defaultMessage: Apply to subfolders +#: components/manage/Rules/Rules +msgid "Apply to subfolders" +msgstr "应用于子文件夹" + +# defaultMessage: Apply working copy +#: components/manage/Toolbar/More +msgid "Apply working copy" +msgstr "申请工作副本" + +# defaultMessage: Are you sure you want to delete this field? +#: components/manage/Widgets/SchemaWidget +msgid "Are you sure you want to delete this field?" +msgstr "确定要删除此字段?" + +# defaultMessage: Are you sure you want to delete this fieldset including all fields? +#: components/manage/Widgets/SchemaWidget +msgid "Are you sure you want to delete this fieldset including all fields?" +msgstr "确定要删除此包括所有字段的字段集?" + +# defaultMessage: Ascending +#: components/manage/Blocks/Search/components/SortOn +#: components/manage/Contents/Contents +msgid "Ascending" +msgstr "上升" + +# defaultMessage: Assignments +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Assignments" +msgstr "分配" + +# defaultMessage: Available +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Available" +msgstr "可用的" + +# defaultMessage: Available content rules: +#: components/manage/Rules/Rules +msgid "Available content rules:" +msgstr "可用的内容规则" + +# defaultMessage: Back +#: components/manage/Aliases/Aliases components/manage/Contents/Contents +#: components/manage/Controlpanels/AddonsControlpanel +#: components/manage/Controlpanels/Aliases +#: components/manage/Controlpanels/ContentType +#: components/manage/Controlpanels/ContentTypeLayout +#: components/manage/Controlpanels/ContentTypes +#: components/manage/Controlpanels/Controlpanel +#: components/manage/Controlpanels/Controlpanels +#: components/manage/Controlpanels/DatabaseInformation +#: components/manage/Controlpanels/ModerateComments +#: components/manage/Controlpanels/Rules/AddRule +#: components/manage/Controlpanels/Rules/ConfigureRule +#: components/manage/Controlpanels/Rules/EditRule +#: components/manage/Controlpanels/Rules/Rules +#: components/manage/Controlpanels/UndoControlpanel +#: components/manage/Controlpanels/UpgradeControlPanel +#: components/manage/Diff/Diff components/manage/History/History +#: components/manage/Multilingual/ManageTranslations +#: components/manage/Preferences/ChangePassword +#: components/manage/Preferences/PersonalPreferences +#: components/manage/Rules/Rules components/manage/Sharing/Sharing +#: components/manage/Sidebar/ObjectBrowserBody +#: components/manage/Toolbar/PersonalTools components/manage/Toolbar/Toolbar +#: components/theme/ContactForm/ContactForm helpers/MessageLabels/MessageLabels +msgid "Back" +msgstr "返回" + +# defaultMessage: Base +#: components/manage/Diff/Diff +msgid "Base" +msgstr "基本" + +# defaultMessage: Base search query +#: components/manage/Blocks/Search/schema +msgid "Base search query" +msgstr "基本搜索查询" + +# defaultMessage: Block +#: components/manage/Sidebar/Sidebar +msgid "Block" +msgstr "块" + +# defaultMessage: Both email address and password are case sensitive, check that caps lock is not enabled. +#: components/theme/Login/Login +msgid "Both email address and password are case sensitive, check that caps lock is not enabled." +msgstr "Email 地址和密码都是区分大小写的,请检查 CapsLock 键。" + +# defaultMessage: Breadcrumbs +#: components/theme/Breadcrumbs/Breadcrumbs +msgid "Breadcrumbs" +msgstr "面包屑导航" + +# defaultMessage: Browse +#: components/manage/Blocks/HeroImageLeft/Edit +#: components/manage/Contents/ContentsUploadModal +#: components/manage/Sidebar/ObjectBrowserNav +msgid "Browse" +msgstr "浏览" + +# defaultMessage: Browse the site, drop an image, or type an URL +#: components/manage/Blocks/Image/Edit +msgid "Browse the site, drop an image, or type an URL" +msgstr "浏览网站,放入一张图片,或者输入一个URL" + +# defaultMessage: By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator. +#: components/manage/Sharing/Sharing +msgid "By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator." +msgstr "默认情况下,条目的权限从父文件夹继承,您可以禁用继承,明确设置权限。总体来说,${inherited}符号表示这是一个继承的值,${global}符号表示这是一个由网站管理员管理的全局角色。" + +# defaultMessage: By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first. +#: components/manage/Contents/Contents +msgid "By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first." +msgstr "删除这些条目,将会破坏下列条目中的链连引用,如果您确定要这样做,建议您先删除这些引用。" + +# defaultMessage: Cache Name +#: components/manage/Controlpanels/DatabaseInformation +msgid "Cache Name" +msgstr "缓存名称" + +# defaultMessage: Can not edit Layout for {type} content-type as it doesn't have support for Volto Blocks enabled +#: components/manage/Controlpanels/ContentTypeLayout +msgid "Can not edit Layout for {type} content-type as it doesn't have support for Volto Blocks enabled" +msgstr "" + +# defaultMessage: Can not edit Layout for {type} content-type as the Blocks behavior is enabled and read-only +#: components/manage/Controlpanels/ContentTypeLayout +msgid "Can not edit Layout for {type} content-type as the Blocks behavior is enabled and read-only" +msgstr "" + +# defaultMessage: Cancel +#: components/manage/Add/Add components/manage/Contents/ContentsUploadModal +#: components/manage/Controlpanels/ContentType +#: components/manage/Controlpanels/ContentTypeLayout +#: components/manage/Controlpanels/ContentTypeSchema +#: components/manage/Controlpanels/Controlpanel +#: components/manage/Controlpanels/Rules/AddRule +#: components/manage/Controlpanels/Rules/EditRule +#: components/manage/Delete/Delete components/manage/Edit/Edit +#: components/manage/Form/ModalForm components/manage/Sharing/Sharing +#: components/theme/Login/Login helpers/MessageLabels/MessageLabels +msgid "Cancel" +msgstr "取消" + +# defaultMessage: Cell +#: components/manage/Blocks/Table/Edit +msgid "Cell" +msgstr "单元格" + +# defaultMessage: Center +#: components/manage/Blocks/Maps/Edit components/manage/Sidebar/AlignBlock +#: components/manage/Widgets/AlignWidget +msgid "Center" +msgstr "中间" + +# defaultMessage: Change Note +#: components/manage/History/History +msgid "Change Note" +msgstr "更改注释" + +# defaultMessage: Change Password +#: components/manage/Preferences/ChangePassword +msgid "Change Password" +msgstr "更改密码" + +# defaultMessage: Change State +#: components/manage/Contents/ContentsWorkflowModal +msgid "Change State" +msgstr "改变状态" + +# defaultMessage: Change workflow state recursively +#: components/manage/Contents/ContentsWorkflowModal +msgid "Change workflow state recursively" +msgstr "递归地更改工作流状态" + +# defaultMessage: Changes applied +#: components/manage/Toolbar/More +msgid "Changes applied." +msgstr "更改已经生效" + +# defaultMessage: Changes saved +#: components/manage/Preferences/ChangePassword +#: components/manage/Preferences/PersonalPreferences +msgid "Changes saved" +msgstr "更改已经保存" + +# defaultMessage: Changes saved. +#: components/manage/Controlpanels/ContentType +#: components/manage/Controlpanels/ContentTypeLayout +#: components/manage/Controlpanels/ContentTypeSchema +#: components/manage/Controlpanels/Controlpanel +msgid "Changes saved." +msgstr "更改已经保存" + +# defaultMessage: Checkbox +#: components/manage/Widgets/SchemaWidget +msgid "Checkbox" +msgstr "复选框" + +# defaultMessage: Choices +#: components/manage/Widgets/SelectWidget +msgid "Choices" +msgstr "选择" + +# defaultMessage: Choose Image +#: components/manage/Sidebar/ObjectBrowserBody +msgid "Choose Image" +msgstr "选择图像" + +# defaultMessage: Choose Target +#: components/manage/Sidebar/ObjectBrowserBody +msgid "Choose Target" +msgstr "选择目标" + +# defaultMessage: Choose a file +#: components/manage/Widgets/FileWidget +msgid "Choose a file" +msgstr "选择一个文件" + +# defaultMessage: Clear +#: components/manage/Blocks/HTML/Edit +msgid "Clear" +msgstr "清除" + +# defaultMessage: Clear filters +#: components/manage/Blocks/Search/components/FilterList +msgid "Clear filters" +msgstr "清除过滤器" + +# defaultMessage: Click to download full sized image +#: components/theme/View/ImageView +msgid "Click to download full sized image" +msgstr "点击下载完整大小的图片" + +# defaultMessage: Close +#: components/manage/Widgets/SelectWidget +msgid "Close" +msgstr "关闭" + +# defaultMessage: Close menu +#: components/theme/Navigation/Navigation +msgid "Close menu" +msgstr "关闭菜单" + +# defaultMessage: Code +#: components/manage/Blocks/HTML/Edit +msgid "Code" +msgstr "代码" + +# defaultMessage: Collapse item +#: components/manage/Widgets/ObjectListWidget +msgid "Collapse item" +msgstr "崩溃的项目" + +# defaultMessage: Collection +#: components/manage/Toolbar/Toolbar +msgid "Collection" +msgstr "查询集" + +# defaultMessage: Color +#: components/manage/Widgets/ColorPickerWidget +msgid "Color" +msgstr "颜色" + +# defaultMessage: Comment +#: components/manage/Controlpanels/ModerateComments +#: components/theme/Comments/CommentEditModal +#: components/theme/Comments/Comments +msgid "Comment" +msgstr "评论" + +# defaultMessage: Commenter +#: components/manage/Controlpanels/ModerateComments +msgid "Commenter" +msgstr "评论者" + +# defaultMessage: Comments +#: components/theme/Comments/Comments +msgid "Comments" +msgstr "评论" + +# defaultMessage: Compare +#: components/manage/Diff/Diff +msgid "Compare" +msgstr "比较" + +# defaultMessage: Condition changed +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Condition changed" +msgstr "条件改变" + +# defaultMessage: Condition: +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Condition: " +msgstr "条件" + +# defaultMessage: Configuration Versions +#: components/manage/Controlpanels/UpgradeControlPanel +msgid "Configuration Versions" +msgstr "配置版本" + +# defaultMessage: Configure Content Rule +#: components/manage/Controlpanels/Rules/EditRule +msgid "Configure Content Rule" +msgstr "配置内容规则" + +# defaultMessage: Configure Content Rule: {title} +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Configure Content Rule: {title}" +msgstr "配置内容规则: {title}" + +# defaultMessage: Configure content rule +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Configure content rule" +msgstr "配置内容规则" + +# defaultMessage: Confirm password +#: components/manage/Preferences/ChangePassword +#: components/theme/PasswordReset/PasswordReset +msgid "Confirm password" +msgstr "确认密码" + +# defaultMessage: Connection refused +#: components/theme/ConnectionRefused/ConnectionRefused +msgid "Connection refused" +msgstr "连接被拒绝" + +# defaultMessage: Contact form +#: components/theme/ContactForm/ContactForm +msgid "Contact form" +msgstr "联系表单" + +# defaultMessage: Contained items +#: components/manage/Blocks/Listing/Edit +msgid "Contained items" +msgstr "包含的项目" + +# defaultMessage: Content +#: components/manage/Controlpanels/Controlpanels +msgid "Content" +msgstr "内容" + +# defaultMessage: Content Rule +#: components/manage/Controlpanels/Rules/Rules +msgid "Content Rule" +msgstr "内容规则" + +# defaultMessage: Content Rules +#: components/manage/Controlpanels/Controlpanels +#: components/manage/Controlpanels/Rules/Rules +msgid "Content Rules" +msgstr "内容规则" + +# defaultMessage: Content rules for {title} +#: components/manage/Rules/Rules +msgid "Content rules for {title}" +msgstr "{title}的内容规则" + +# defaultMessage: Content rules from parent folders +#: components/manage/Rules/Rules +msgid "Content rules from parent folders" +msgstr "父文件夹的内容规则" + +# defaultMessage: Content type created +#: components/manage/Controlpanels/ContentTypes +msgid "Content type created" +msgstr "内容类型已创建" + +# defaultMessage: Content type deleted +#: components/manage/Controlpanels/ContentTypes +msgid "Content type deleted" +msgstr "内容类型已删除" + +# defaultMessage: Contents +#: components/manage/Contents/Contents components/manage/Toolbar/Toolbar +msgid "Contents" +msgstr "内容" + +# defaultMessage: Controls +#: components/manage/Blocks/Search/schema +msgid "Controls" +msgstr "控制" + +# defaultMessage: Copy +#: components/manage/Actions/Actions components/manage/Contents/Contents +#: components/manage/Contents/ContentsItem +msgid "Copy" +msgstr "复制" + +# defaultMessage: undefined +#: helpers/MessageLabels/MessageLabels +msgid "Copy blocks" +msgstr "复制块" + +# defaultMessage: Copyright +#: components/theme/Footer/Footer +msgid "Copyright" +msgstr "版权" + +# defaultMessage: Copyright statement or other rights information on this item. +#: components/manage/Contents/ContentsPropertiesModal +msgid "Copyright statement or other rights information on this item." +msgstr "本内容的版权信息" + +# defaultMessage: Create working copy +#: components/manage/Toolbar/More +msgid "Create working copy" +msgstr "创建工作副本" + +# defaultMessage: Created by {creator} on {date} +#: components/manage/WorkingCopyToastsFactory/WorkingCopyToastsFactory +msgid "Created by {creator} on {date}" +msgstr "由{creator}在{date}时创建" + +# defaultMessage: Created on +#: components/manage/Contents/Contents +msgid "Created on" +msgstr "创建于" + +# defaultMessage: Creator +#: components/manage/Contents/Contents +msgid "Creator" +msgstr "创建者" + +# defaultMessage: Creators +#: components/manage/Contents/ContentsPropertiesModal +msgid "Creators" +msgstr "创建者" + +# defaultMessage: Criteria +#: components/manage/Widgets/QuerystringWidget +#: components/manage/Widgets/QueryWidget +msgid "Criteria" +msgstr "标准" + +# defaultMessage: Current active configuration +#: components/manage/Controlpanels/UpgradeControlPanel +msgid "Current active configuration" +msgstr "当前活动的配置" + +# defaultMessage: Current filters applied +#: components/manage/Blocks/Search/components/FilterList +msgid "Current filters applied" +msgstr "当前应用的过滤器" + +# defaultMessage: Current password +#: components/manage/Preferences/ChangePassword +msgid "Current password" +msgstr "当前的密码" + +# defaultMessage: Cut +#: components/manage/Actions/Actions components/manage/Contents/Contents +#: components/manage/Contents/ContentsItem +msgid "Cut" +msgstr "剪切" + +# defaultMessage: undefined +#: helpers/MessageLabels/MessageLabels +msgid "Cut blocks" +msgstr "剪切块" + +# defaultMessage: Daily +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Daily" +msgstr "每日" + +# defaultMessage: Database +#: components/manage/Controlpanels/Controlpanels +msgid "Database" +msgstr "数据库" + +# defaultMessage: Database Information +#: components/manage/Controlpanels/DatabaseInformation +msgid "Database Information" +msgstr "数据库信息" + +# defaultMessage: Database Location +#: components/manage/Controlpanels/DatabaseInformation +msgid "Database Location" +msgstr "数据库位置" + +# defaultMessage: Database Size +#: components/manage/Controlpanels/DatabaseInformation +msgid "Database Size" +msgstr "数据库大小" + +# defaultMessage: Database main +#: components/manage/Controlpanels/DatabaseInformation +msgid "Database main" +msgstr "" + +# defaultMessage: Date +#: components/manage/Controlpanels/Aliases +#: components/manage/Controlpanels/ModerateComments +#: components/manage/Widgets/DatetimeWidget +msgid "Date" +msgstr "日期" + +# defaultMessage: Date (newest first) +#: components/theme/Search/Search +msgid "Date (newest first)" +msgstr "日期(最新在前)" + +# defaultMessage: Default +#: components/manage/Contents/ContentsPropertiesModal +#: components/manage/Contents/ContentsRenameModal +#: components/manage/Contents/ContentsTagsModal +#: components/manage/Contents/ContentsWorkflowModal +#: components/manage/Controlpanels/UndoControlpanel +#: components/manage/Preferences/ChangePassword +#: components/manage/Preferences/PersonalPreferences +#: components/manage/Widgets/SchemaWidget +#: components/manage/Widgets/SelectWidget +#: components/manage/Widgets/WysiwygWidget +#: components/theme/Comments/CommentEditModal +#: components/theme/Comments/Comments components/theme/ContactForm/ContactForm +#: components/theme/PasswordReset/PasswordReset +#: components/theme/PasswordReset/RequestPasswordReset +#: components/theme/Register/Register +msgid "Default" +msgstr "默认" + +# defaultMessage: Default view +#: config/Views +msgid "Default view" +msgstr "默认视图" + +# defaultMessage: Delete +#: components/manage/Contents/Contents components/manage/Contents/ContentsItem +#: components/manage/Controlpanels/ContentTypesActions +#: components/manage/Controlpanels/Groups/RenderGroups +#: components/manage/Controlpanels/ModerateComments +#: components/manage/Controlpanels/Users/RenderUsers +#: components/manage/Delete/Delete components/manage/Widgets/FormFieldWrapper +#: components/manage/Widgets/ObjectBrowserWidget +#: components/manage/Widgets/WysiwygWidget components/theme/Comments/Comments +msgid "Delete" +msgstr "删除" + +# defaultMessage: Delete Group +#: helpers/MessageLabels/MessageLabels +msgid "Delete Group" +msgstr "删除组" + +# defaultMessage: Delete Type +#: components/manage/Controlpanels/ContentTypes +msgid "Delete Type" +msgstr "删除类型" + +# defaultMessage: Delete User +#: helpers/MessageLabels/MessageLabels +msgid "Delete User" +msgstr "删除用户" + +# defaultMessage: Action deleted +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Delete action" +msgstr "删除操作" + +# defaultMessage: undefined +#: helpers/MessageLabels/MessageLabels +msgid "Delete blocks" +msgstr "删除块" + +# defaultMessage: Delete col +#: components/manage/Blocks/Table/Edit +msgid "Delete col" +msgstr "删除 col" + +# defaultMessage: Condition deleted +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Delete condition" +msgstr "删除条件" + +# defaultMessage: Delete row +#: components/manage/Blocks/Table/Edit +msgid "Delete row" +msgstr "删除条件" + +# defaultMessage: Deleted +#: components/manage/Controlpanels/Rules/Rules +msgid "Deleted" +msgstr "已删除" + +# defaultMessage: Depth +#: components/manage/Widgets/QuerystringWidget +msgid "Depth" +msgstr "深度" + +# defaultMessage: Descending +#: components/manage/Blocks/Search/components/SortOn +#: components/manage/Contents/Contents +msgid "Descending" +msgstr "降序" + +# defaultMessage: Description +#: components/manage/Blocks/HeroImageLeft/Edit +#: components/manage/Controlpanels/ContentTypes +#: components/manage/Widgets/SchemaWidget +#: components/manage/Widgets/SelectWidget +#: components/manage/Widgets/WysiwygWidget components/theme/View/TabularView +#: helpers/MessageLabels/MessageLabels +msgid "Description" +msgstr "描述" + +# defaultMessage: Diff +#: components/manage/Diff/Diff +msgid "Diff" +msgstr "不同" + +# defaultMessage: Difference between revision {one} and {two} of {title} +#: components/manage/Diff/Diff +msgid "Difference between revision {one} and {two} of {title}" +msgstr "{title}版本 {one} 和 {two} 之间的区别" + +# defaultMessage: Disable +#: components/manage/Rules/Rules +msgid "Disable" +msgstr "禁用" + +# defaultMessage: Disable apply to subfolders +#: components/manage/Rules/Rules +msgid "Disable apply to subfolders" +msgstr "禁用于子文件夹" + +# defaultMessage: Disabled +#: components/manage/Rules/Rules +msgid "Disabled" +msgstr "禁用" + +# defaultMessage: Disabled apply to subfolders +#: components/manage/Rules/Rules +msgid "Disabled apply to subfolders" +msgstr "禁用于子文件夹" + +# defaultMessage: Distributed under the {license}. +#: components/theme/Footer/Footer +msgid "Distributed under the {license}." +msgstr "使用{license} 发布" + +# defaultMessage: Add border to inner columns +#: components/manage/Blocks/Table/Edit +msgid "Divide each row into separate cells" +msgstr "将每一行分成独立的单元格" + +# defaultMessage: Do you really want to delete the following items? +#: components/manage/Contents/Contents +msgid "Do you really want to delete the following items?" +msgstr "确定要删除这个条目吗?" + +# defaultMessage: Do you really want to delete the group {groupname}? +#: components/manage/Controlpanels/Groups/GroupsControlpanel +msgid "Do you really want to delete the group {groupname}?" +msgstr "确定要删除这个组 {groupname}吗?" + +# defaultMessage: Do you really want to delete type {typename}? +#: components/manage/Controlpanels/ContentTypes +msgid "Do you really want to delete the type {typename}?" +msgstr "确定要删除这个类型 {typename}吗?" + +# defaultMessage: Do you really want to delete the user {username}? +#: components/manage/Controlpanels/Users/UsersControlpanel +msgid "Do you really want to delete the user {username}?" +msgstr "确定要删除这个用户 {username}吗?" + +# defaultMessage: Do you really want to delete this item? +#: components/manage/Delete/Delete +msgid "Do you really want to delete this item?" +msgstr "确定要删除这个条目吗" + +# defaultMessage: Document +#: components/manage/Multilingual/TranslationObject +#: components/manage/Sidebar/Sidebar +msgid "Document" +msgstr "文档" + +# defaultMessage: Document view +#: config/Views +msgid "Document view" +msgstr "文档视图" + +# defaultMessage: Download Event +#: components/theme/EventDetails/EventDetails +msgid "Download Event" +msgstr "下载事件" + +# defaultMessage: Drag and drop files from your computer onto this area or click the “Browse” button. +#: components/manage/Contents/ContentsUploadModal +msgid "Drag and drop files from your computer onto this area or click the “Browse” button." +msgstr "从你的电脑中拖放文件到此区域或单击 “浏览” 按钮" + +# defaultMessage: Drop file here to replace the existing file +#: components/manage/Widgets/FileWidget +msgid "Drop file here to replace the existing file" +msgstr "在此处放置文件以替换现有文件" + +# defaultMessage: Drop file here to upload a new file +#: components/manage/Widgets/FileWidget +msgid "Drop file here to upload a new file" +msgstr "在此处放置文件来上传新文件" + +# defaultMessage: Drop files here ... +#: components/manage/Widgets/FileWidget +msgid "Drop files here ..." +msgstr "在此处放置文件 ..." + +# defaultMessage: Dry run selected, transaction aborted. +#: components/manage/Controlpanels/UpgradeControlPanel +msgid "Dry run selected, transaction aborted." +msgstr ",事务已被终止。" + +# defaultMessage: E-mail +#: components/theme/Register/Register +msgid "E-mail" +msgstr "E-mail" + +# defaultMessage: E-mail addresses do not match. +#: components/theme/PasswordReset/PasswordReset +msgid "E-mail addresses do not match." +msgstr "邮箱地址不匹配" + +# defaultMessage: Edit +#: components/manage/Contents/ContentsItem +#: components/manage/Controlpanels/ContentTypesActions +#: components/manage/Controlpanels/ModerateComments +#: components/manage/Controlpanels/Rules/EditRule +#: components/manage/Toolbar/Toolbar components/manage/Widgets/FormFieldWrapper +#: components/manage/Widgets/ObjectBrowserWidget +#: components/theme/Comments/Comments +msgid "Edit" +msgstr "编辑" + +# defaultMessage: Edit Rule +#: components/manage/Controlpanels/Rules/EditRule +msgid "Edit Rule" +msgstr "编辑规则" + +# defaultMessage: Edit comment +#: components/theme/Comments/CommentEditModal +msgid "Edit comment" +msgstr "编辑评论" + +# defaultMessage: Edit field +#: components/manage/Widgets/SchemaWidget +msgid "Edit field" +msgstr "编辑字段" + +# defaultMessage: Edit fieldset +#: components/manage/Widgets/SchemaWidget +msgid "Edit fieldset" +msgstr "编辑字段集" + +# defaultMessage: Edit recurrence +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Edit recurrence" +msgstr "编辑重现" + +# defaultMessage: Edit values +#: components/manage/Form/InlineForm +msgid "Edit values" +msgstr "编辑值" + +# defaultMessage: Edit {title} +#: components/manage/Edit/Edit +msgid "Edit {title}" +msgstr "编辑 {title}" + +# defaultMessage: Email +#: helpers/MessageLabels/MessageLabels +msgid "Email" +msgstr "Email" + +# defaultMessage: Email sent +#: components/theme/ContactForm/ContactForm +msgid "Email sent" +msgstr "发送邮件" + +# defaultMessage: Embed code error, please follow the instructions and try again. +#: components/manage/Blocks/Maps/Edit +msgid "Embed code error, please follow the instructions and try again." +msgstr "嵌入代码错误,请按照说明重试" + +# defaultMessage: Empty object list +#: components/manage/Widgets/ObjectListWidget +msgid "Empty object list" +msgstr "空的对象列表" + +# defaultMessage: Enable +#: components/manage/Rules/Rules +msgid "Enable" +msgstr "启用" + +# defaultMessage: Enable editable Blocks +#: components/manage/Controlpanels/ContentTypeLayout +msgid "Enable editable Blocks" +msgstr "启用可编辑块" + +# defaultMessage: Enabled +#: components/manage/Rules/Rules +msgid "Enabled" +msgstr "启用" + +# defaultMessage: Enabled here? +#: components/manage/Rules/Rules +msgid "Enabled here?" +msgstr "在这里启用?" + +# defaultMessage: Enabled? +#: components/manage/Rules/Rules +msgid "Enabled?" +msgstr "启用?" + +# defaultMessage: End Date +#: components/manage/Blocks/Search/components/DateRangeFacet +#: components/manage/Contents/Contents +msgid "End Date" +msgstr "结束日期" + +# defaultMessage: Enter URL or select an item +#: components/manage/AnchorPlugin/components/LinkButton/AddLinkForm +msgid "Enter URL or select an item" +msgstr "输入 URL 或选择一个项目" + +# defaultMessage: Enter a username above to search or click 'Show All' +#: helpers/MessageLabels/MessageLabels +msgid "Enter a username above to search or click 'Show All'" +msgstr "输入用户名进行搜索或点击显示全部" + +# defaultMessage: Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. +#: components/theme/Register/Register +msgid "Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere." +msgstr "输入一个 Email 地址。这会成为您的登录名。我们尊重您的隐私,不会向第三方透漏您的个人信息。" + +# defaultMessage: Enter full name, e.g. John Smith. +#: components/theme/Register/Register +msgid "Enter full name, e.g. John Smith." +msgstr "输入您的姓名,如:张三" + +# defaultMessage: Enter map Embed Code +#: components/manage/Blocks/Maps/Edit +msgid "Enter map Embed Code" +msgstr "输入 map 嵌入代码" + +# defaultMessage: Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target. +#: components/manage/Controlpanels/Aliases +msgid "Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target." +msgstr "输入目标的绝对路径。路径必须以‘/’开头。目标必须存在,或者是指向目标的现有可选的url路径。" + +# defaultMessage: Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring. +#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases +msgid "Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring." +msgstr "输入已存在替代 URL 的绝对路径。路径必须以 '/' 开头。只有导致404 未找到页面的url才会发生重定向。" + +# defaultMessage: Enter your current password. +#: components/manage/Preferences/ChangePassword +msgid "Enter your current password." +msgstr "输入当前密码" + +# defaultMessage: Enter your email for verification. +#: components/theme/PasswordReset/PasswordReset +msgid "Enter your email for verification." +msgstr "输入你的邮箱进行验证" + +# defaultMessage: Enter your new password. Minimum 5 characters. +#: components/manage/Preferences/ChangePassword +#: components/theme/PasswordReset/PasswordReset +msgid "Enter your new password. Minimum 5 characters." +msgstr "输入你的新密码。最少5个字符" + +# defaultMessage: Enter your username for verification. +#: components/theme/PasswordReset/PasswordReset +msgid "Enter your username for verification." +msgstr "输入你的用户名进行验证" + +# defaultMessage: Error +#: components/manage/Add/Add components/manage/Controlpanels/AddonsControlpanel +#: components/manage/Controlpanels/ContentTypeSchema +#: components/manage/Controlpanels/UndoControlpanel components/manage/Edit/Edit +#: components/manage/Form/InlineForm components/manage/Toolbar/More +#: components/manage/Widgets/SchemaWidget +#: components/theme/ContactForm/ContactForm components/theme/Login/Login +#: helpers/MessageLabels/MessageLabels +msgid "Error" +msgstr "错误" + +# defaultMessage: Error +#: components/manage/Controlpanels/Aliases +msgid "ErrorHeader" +msgstr "" + +# defaultMessage: Event +#: components/manage/Controlpanels/Rules/Rules +msgid "Event" +msgstr "事件" + +# defaultMessage: Event listing +#: config/Views +msgid "Event listing" +msgstr "事件列表" + +# defaultMessage: Event view +#: config/Views +msgid "Event view" +msgstr "事件视图" + +# defaultMessage: Exclude from navigation +#: components/manage/Contents/ContentsPropertiesModal +msgid "Exclude from navigation" +msgstr "从导航中排除" + +# defaultMessage: Exclude this occurence +#: components/manage/Widgets/RecurrenceWidget/Occurences +msgid "Exclude this occurence" +msgstr "排除这个事件" + +# defaultMessage: Excluded from navigation +#: components/manage/Contents/Contents +msgid "Excluded from navigation" +msgstr "从导航中排除" + +# defaultMessage: Existing alternative urls for this item +#: components/manage/Aliases/Aliases +msgid "Existing alternative urls for this item" +msgstr "此项目现有可选的urls" + +# defaultMessage: Expand sidebar +#: components/manage/Sidebar/Sidebar +msgid "Expand sidebar" +msgstr "扩大侧边栏" + +# defaultMessage: Expiration Date +#: components/manage/Contents/ContentsPropertiesModal +msgid "Expiration Date" +msgstr "失效日期" + +# defaultMessage: Expiration date +#: components/manage/Contents/Contents +msgid "Expiration date" +msgstr "失效日期" + +# defaultMessage: Expired +#: components/manage/Contents/ContentsItem +msgid "Expired" +msgstr "失效" + +# defaultMessage: External URL +#: components/manage/Blocks/LeadImage/LeadImageSidebar +msgid "External URL" +msgstr "失效的URL" + +# defaultMessage: Facet +#: components/manage/Blocks/Search/schema +msgid "Facet" +msgstr "" + +# defaultMessage: Facet widget +#: components/manage/Blocks/Search/schema +msgid "Facet widget" +msgstr "" + +# defaultMessage: Facets +#: components/manage/Blocks/Search/schema +msgid "Facets" +msgstr "" + +# defaultMessage: Facets on left side +#: config/Blocks +msgid "Facets on left side" +msgstr "" + +# defaultMessage: Facets on right side +#: config/Blocks +msgid "Facets on right side" +msgstr "" + +# defaultMessage: Facets on top +#: config/Blocks +msgid "Facets on top" +msgstr "" + +# defaultMessage: Failed to undo transactions +#: components/manage/Controlpanels/UndoControlpanel +msgid "Failed To Undo Transactions" +msgstr "撤销事务失败" + +# defaultMessage: Field +#: components/manage/Blocks/Search/schema +msgid "Field" +msgstr "字段" + +# defaultMessage: File +#: components/manage/Toolbar/Toolbar +msgid "File" +msgstr "文件" + +# defaultMessage: File size +#: components/manage/Contents/ContentsUploadModal +msgid "File size" +msgstr "文件大小" + +# defaultMessage: File view +#: config/Views +msgid "File view" +msgstr "文件视图" + +# defaultMessage: Filename +#: components/manage/Contents/ContentsUploadModal +msgid "Filename" +msgstr "文件名" + +# defaultMessage: Filter Rules: +#: components/manage/Controlpanels/Rules/Rules +msgid "Filter Rules:" +msgstr "过滤规则" + +# defaultMessage: Filter by prefix +#: components/manage/Controlpanels/Aliases +msgid "Filter by prefix" +msgstr "按前缀过滤" + +# defaultMessage: Filter users by groups +#: helpers/MessageLabels/MessageLabels +msgid "Filter users by groups" +msgstr "按组过滤用户" + +# defaultMessage: Filter… +#: components/manage/Contents/Contents +msgid "Filter…" +msgstr "过滤" + +# defaultMessage: First +#: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField +msgid "First" +msgstr "首个" + +# defaultMessage: Fixed width columns +#: components/manage/Blocks/Table/Edit +msgid "Fixed width table cells" +msgstr "固定表格单元格宽度" + +# defaultMessage: Fold +#: components/manage/BlockChooser/BlockChooser +msgid "Fold" +msgstr "折叠" + +# defaultMessage: Folder +#: components/manage/Contents/Contents +msgid "Folder" +msgstr "文件夹" + +# defaultMessage: Folder listing +#: config/Views +msgid "Folder listing" +msgstr "文件夹列表" + +# defaultMessage: Forbidden +#: components/theme/Forbidden/Forbidden +msgid "Forbidden" +msgstr "禁止" + +# defaultMessage: Fourth +#: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField +msgid "Fourth" +msgstr "第四" + +# defaultMessage: From +#: components/theme/ContactForm/ContactForm +msgid "From" +msgstr "" + +# defaultMessage: Full +#: components/manage/Blocks/Maps/Edit components/manage/Sidebar/AlignBlock +#: components/manage/Widgets/AlignWidget +msgid "Full" +msgstr "" + +# defaultMessage: Full Name +#: components/theme/Register/Register +msgid "Full Name" +msgstr "姓名" + +# defaultMessage: Fullname +#: helpers/MessageLabels/MessageLabels +msgid "Fullname" +msgstr "姓名" + +# defaultMessage: GNU GPL license +#: components/theme/Footer/Footer +msgid "GNU GPL license" +msgstr "GNU GPL 许可" + +# defaultMessage: General +#: components/manage/Controlpanels/Controlpanels +msgid "General" +msgstr "常规" + +# defaultMessage: Global role +#: components/manage/Sharing/Sharing +msgid "Global role" +msgstr "全局角色" + +# defaultMessage: Google Maps Embedded Block +#: components/manage/Blocks/Maps/Edit +msgid "Google Maps Embedded Block" +msgstr "Google 地图嵌入块" + +# defaultMessage: Group +#: components/manage/Sharing/Sharing +msgid "Group" +msgstr "组" + +# defaultMessage: Group created +#: helpers/MessageLabels/MessageLabels +msgid "Group created" +msgstr "组已创建" + +# defaultMessage: Group roles updated +#: helpers/MessageLabels/MessageLabels +msgid "Group roles updated" +msgstr "组角色更新" + +# defaultMessage: Groupname +#: components/manage/Controlpanels/Groups/GroupsControlpanel +#: helpers/MessageLabels/MessageLabels +msgid "Groupname" +msgstr "组名" + +# defaultMessage: Groups +#: components/manage/Controlpanels/Controlpanels +#: components/manage/Controlpanels/Groups/GroupsControlpanel +#: helpers/MessageLabels/MessageLabels +msgid "Groups" +msgstr "组" + +# defaultMessage: Groups are logical collections of users, such as departments and business units. Groups are not directly related to permissions on a global level, you normally use Roles for that - and let certain Groups have a particular role. The symbol{plone_svg}indicates a role inherited from membership in another group. +#: components/manage/Controlpanels/Groups/GroupsControlpanel +msgid "Groups are logical collections of users, such as departments and business units. Groups are not directly related to permissions on a global level, you normally use Roles for that - and let certain Groups have a particular role. The symbol{plone_svg}indicates a role inherited from membership in another group." +msgstr "组是用户的逻辑集合,如部门和商务小组。他们不直接和某个权限关联,您应该用角色分配权限,然后让组拥有某个角色。符号{plone_svg}表示从另一个组的成员继承的角色" + +# defaultMessage: Header cell +#: components/manage/Blocks/Table/Edit +msgid "Header cell" +msgstr "标题单元格" + +# defaultMessage: Headline +#: components/manage/Blocks/Listing/schema +#: components/manage/Blocks/Search/schema +msgid "Headline" +msgstr "标题" + +# defaultMessage: Headline level +#: components/manage/Blocks/Listing/schema +msgid "Headline level" +msgstr "标题级别" + +# defaultMessage: Hidden facets will still filter the results if proper parameters are passed in URLs +#: components/manage/Blocks/Search/schema +msgid "Hidden facets will still filter the results if proper parameters are passed in URLs" +msgstr "" + +# defaultMessage: Hide Replies +#: components/theme/Comments/Comments +msgid "Hide Replies" +msgstr "隐藏回复" + +# defaultMessage: Hide facet? +#: components/manage/Blocks/Search/schema +msgid "Hide facet?" +msgstr "" + +# defaultMessage: History +#: components/manage/History/History components/manage/Toolbar/More +msgid "History" +msgstr "历史" + +# defaultMessage: # +#: components/manage/History/History +msgid "History Version Number" +msgstr "历史版本号" + +# defaultMessage: History of {title} +#: components/manage/History/History +msgid "History of {title}" +msgstr "历史{title}" + +# defaultMessage: Home +#: components/manage/Contents/Contents +#: components/manage/Contents/ContentsBreadcrumbs +#: components/manage/Contents/ContentsBreadcrumbsHomeItem +#: components/theme/Breadcrumbs/Breadcrumbs +msgid "Home" +msgstr "首页" + +# defaultMessage: ID +#: components/manage/Contents/Contents +msgid "ID" +msgstr "" + +# defaultMessage: If all of the following conditions are met: +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "If all of the following conditions are met:" +msgstr "如果满足以下所有条件" + +# defaultMessage: If selected, this item will not appear in the navigation tree +#: components/manage/Contents/ContentsPropertiesModal +msgid "If selected, this item will not appear in the navigation tree" +msgstr "如果选中,此条目不会在导航树中显示" + +# defaultMessage: If this date is in the future, the content will not show up in listings and searches until this date. +#: components/manage/Contents/ContentsPropertiesModal +msgid "If this date is in the future, the content will not show up in listings and searches until this date." +msgstr "该条目发布的日期。如果未选择日期,将会立即发布该条目。" + +# defaultMessage: If you are certain this user has abandoned the object, you may unlock the object. You will then be able to edit it. +#: components/manage/LockingToastsFactory/LockingToastsFactory +msgid "If you are certain this user has abandoned the object, you may unlock the object. You will then be able to edit it." +msgstr "如果您确定该用户已放弃该对象,则可以解锁该对象。然后您就可以编辑它了。" + +# defaultMessage: If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}. +#: components/theme/NotFound/NotFound +#: components/theme/Unauthorized/Unauthorized +msgid "If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}." +msgstr "如果您确定访问的Web地址正确,但是遇到错误,请联系{site_admin}。" + +# defaultMessage: Image +#: components/manage/Blocks/HeroImageLeft/Edit +#: components/manage/Blocks/Image/ImageSidebar +#: components/manage/Blocks/Image/schema +#: components/manage/Blocks/LeadImage/LeadImageSidebar +msgid "Image" +msgstr "图像" + +# defaultMessage: Image gallery +#: config/Blocks +msgid "Image gallery" +msgstr "图像库" + +# defaultMessage: Image size +#: components/manage/Blocks/Image/schema +msgid "Image size" +msgstr "图像大小" + +# defaultMessage: Image view +#: config/Views +msgid "Image view" +msgstr "图像视图" + +# defaultMessage: Include this occurence +#: components/manage/Widgets/RecurrenceWidget/Occurences +msgid "Include this occurence" +msgstr "包括这个事件" + +# defaultMessage: Info +#: components/manage/Controlpanels/ContentType +#: components/manage/Controlpanels/ContentTypeLayout +#: components/manage/Controlpanels/ContentTypeSchema +#: components/manage/Controlpanels/Controlpanel +msgid "Info" +msgstr "信息" + +# defaultMessage: You have selected the option 'many users' or 'many groups'. Thus this control panel asks for input to show users and groups. If you want to see users and groups instantaneous, head over to user group settings. See the button on the left. +#: components/manage/Controlpanels/Users/UserGroupMembershipControlPanel +msgid "InfoUserGroupSettings" +msgstr "" + +# defaultMessage: Inherit permissions from higher levels +#: components/manage/Sharing/Sharing +msgid "Inherit permissions from higher levels" +msgstr "从上层继承权限" + +# defaultMessage: Inherited value +#: components/manage/Sharing/Sharing +msgid "Inherited value" +msgstr "继承的值" + +# defaultMessage: Insert col after +#: components/manage/Blocks/Table/Edit +msgid "Insert col after" +msgstr "在后面插入col" + +# defaultMessage: Insert col before +#: components/manage/Blocks/Table/Edit +msgid "Insert col before" +msgstr "在前面插入col" + +# defaultMessage: Insert row after +#: components/manage/Blocks/Table/Edit +msgid "Insert row after" +msgstr "在后面插入行" + +# defaultMessage: Insert row before +#: components/manage/Blocks/Table/Edit +msgid "Insert row before" +msgstr "在前面插入行" + +# defaultMessage: Install +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Install" +msgstr "安装" + +# defaultMessage: Installed +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Installed" +msgstr "已安装" + +# defaultMessage: Installed version +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Installed version" +msgstr "已安装版本" + +# defaultMessage: Installing a third party add-on +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Installing a third party add-on" +msgstr "正在安装第三方附加组件" + +# defaultMessage: days +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Interval Daily" +msgstr "" + +# defaultMessage: Month(s) +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Interval Monthly" +msgstr "" + +# defaultMessage: week(s) +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Interval Weekly" +msgstr "" + +# defaultMessage: year(s) +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Interval Yearly" +msgstr "" + +# defaultMessage: Invalid block - Will be removed on saving +#: components/theme/View/RenderBlocks +msgid "Invalid Block" +msgstr "无效的块" + +# defaultMessage: Item batch size +#: components/manage/Widgets/QuerystringWidget +msgid "Item batch size" +msgstr "条目批量大小" + +# defaultMessage: Item succesfully moved. +#: components/manage/Contents/Contents +msgid "Item succesfully moved." +msgstr "成功移除条目。" + +# defaultMessage: Item(s) copied. +#: components/manage/Contents/Contents +msgid "Item(s) copied." +msgstr "条目已复制" + +# defaultMessage: Item(s) cut. +#: components/manage/Contents/Contents +msgid "Item(s) cut." +msgstr "条目已剪切" + +# defaultMessage: Item(s) has been updated. +#: components/manage/Contents/Contents +msgid "Item(s) has been updated." +msgstr "条目已更新" + +# defaultMessage: Item(s) pasted. +#: components/manage/Actions/Actions components/manage/Contents/Contents +msgid "Item(s) pasted." +msgstr "条目已粘贴" + +# defaultMessage: Item(s) state has been updated. +#: components/manage/Contents/Contents +msgid "Item(s) state has been updated." +msgstr "条目状态已更新" + +# defaultMessage: Items +#: components/manage/Controlpanels/ContentTypes +msgid "Items" +msgstr "条目" + +# defaultMessage: Items must be unique. +#: components/manage/Form/ModalForm helpers/MessageLabels/MessageLabels +msgid "Items must be unique." +msgstr "条目必须是独特的" + +# defaultMessage: Items to be deleted: +#: components/manage/Contents/Contents +msgid "Items to be deleted:" +msgstr "需要删除的条目" + +# defaultMessage: Label +#: components/manage/Blocks/Search/schema +msgid "Label" +msgstr "标签" + +# defaultMessage: Language +#: components/manage/Preferences/PersonalPreferences +msgid "Language" +msgstr "语言" + +# defaultMessage: Language independent field. +#: components/manage/Widgets/FormFieldWrapper +msgid "Language independent field." +msgstr "语言独立字段" + +# defaultMessage: Large +#: components/manage/Widgets/ImageSizeWidget +msgid "Large" +msgstr "大" + +# defaultMessage: Last +#: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField +msgid "Last" +msgstr "最后" + +# defaultMessage: Last comment date +#: components/manage/Contents/Contents +msgid "Last comment date" +msgstr "最后备注日期" + +# defaultMessage: Last modified +#: components/manage/Contents/ContentsUploadModal +msgid "Last modified" +msgstr "最后修改日期" + +# defaultMessage: Latest available configuration +#: components/manage/Controlpanels/UpgradeControlPanel +msgid "Latest available configuration" +msgstr "最新可用配置" + +# defaultMessage: Latest version +#: components/manage/Controlpanels/AddonsControlpanel +msgid "Latest version" +msgstr "最新版本" + +# defaultMessage: Layout +#: components/manage/Controlpanels/ContentTypesActions +msgid "Layout" +msgstr "布局" + +# defaultMessage: Lead Image +#: components/manage/Blocks/LeadImage/LeadImageSidebar +msgid "Lead Image" +msgstr "首图" + +# defaultMessage: Left +#: components/manage/Blocks/Maps/Edit components/manage/Sidebar/AlignBlock +#: components/manage/Widgets/AlignWidget +msgid "Left" +msgstr "" + +# defaultMessage: Link +#: components/manage/Toolbar/Toolbar +msgid "Link" +msgstr "链接" + +# defaultMessage: Link more +#: components/manage/Blocks/HeroImageLeft/schema +#: components/manage/Blocks/Listing/schema +msgid "Link more" +msgstr "链接更多" + +# defaultMessage: Link redirect view +#: config/Views +msgid "Link redirect view" +msgstr "链接重定向视图" + +# defaultMessage: Link Title +#: components/manage/Blocks/HeroImageLeft/schema +#: components/manage/Blocks/Listing/schema +msgid "Link title" +msgstr "链接标题" + +# defaultMessage: Link to +#: components/manage/Blocks/HeroImageLeft/schema +#: components/manage/Blocks/Image/schema +#: components/manage/Blocks/LeadImage/LeadImageSidebar +#: components/manage/Blocks/Listing/schema +msgid "Link to" +msgstr "链接至" + +# defaultMessage: Link translation for +#: components/manage/Multilingual/ManageTranslations +msgid "Link translation for" +msgstr "链接翻译" + +# defaultMessage: Listing +#: components/manage/Blocks/Listing/schema +msgid "Listing" +msgstr "列表" + +# defaultMessage: Listing view +#: config/Views +msgid "Listing view" +msgstr "列表视图" + +# defaultMessage: Load more... +#: components/theme/Comments/Comments +msgid "Load more" +msgstr "加载更多" + +# defaultMessage: Loading. +#: components/manage/Form/ModalForm +msgid "Loading" +msgstr "加载中" + +# defaultMessage: Login +#: components/theme/Login/Login +msgid "Log In" +msgstr "登录" + +# defaultMessage: Log in +#: components/theme/Anontools/Anontools components/theme/Login/Login +msgid "Log in" +msgstr "登录" + +# defaultMessage: Logged out +#: components/theme/Logout/Logout +msgid "Logged out" +msgstr "退出" + +# defaultMessage: Login +#: components/theme/Login/Login +msgid "Login" +msgstr "登录" + +# defaultMessage: Login Failed +#: components/theme/Login/Login +msgid "Login Failed" +msgstr "登陆失败" + +# defaultMessage: Login Name +#: components/theme/Login/Login +msgid "Login Name" +msgstr "登录名" + +# defaultMessage: Logout +#: components/manage/Toolbar/PersonalTools +msgid "Logout" +msgstr "退出" + +# defaultMessage: Made by {creator} on {date}. This is not a working copy anymore, but the main content. +#: components/manage/Toolbar/More +msgid "Made by {creator} on {date}. This is not a working copy anymore, but the main content." +msgstr "由{creator}在{date}时创建。这不再是一个工作副本,而是一个主要内容。" + +# defaultMessage: Reduce cell padding +#: components/manage/Blocks/Table/Edit +msgid "Make the table compact" +msgstr "使表格紧凑" + +# defaultMessage: Manage Translations +#: components/manage/Multilingual/ManageTranslations +#: components/manage/Toolbar/More +msgid "Manage Translations" +msgstr "管理翻译" + +# defaultMessage: Manage content… +#: components/manage/Toolbar/More +msgid "Manage content…" +msgstr "管理内容..." + +# defaultMessage: Manage translations for {title} +#: components/manage/Multilingual/ManageTranslations +msgid "Manage translations for {title}" +msgstr "管理{title}的翻译" + +# defaultMessage: Manual +#: components/manage/Controlpanels/Aliases +msgid "Manual" +msgstr "" + +# defaultMessage: Manually or automatically added? +#: components/manage/Controlpanels/Aliases +msgid "Manually or automatically added?" +msgstr "手动添加还是自动添加?" + +# defaultMessage: Maps +#: components/manage/Blocks/Maps/MapsSidebar +#: components/manage/Blocks/Maps/schema +msgid "Maps" +msgstr "映射" + +# defaultMessage: Maps URL +#: components/manage/Blocks/Maps/schema +msgid "Maps URL" +msgstr "映射URL" + +# defaultMessage: Maximum length is {len}. +#: helpers/MessageLabels/MessageLabels +msgid "Maximum length is {len}." +msgstr "最大长度为{len}。" + +# defaultMessage: Maximum value is {len}. +#: helpers/MessageLabels/MessageLabels +msgid "Maximum value is {len}." +msgstr "最大值为{len}。" + +# defaultMessage: Medium +#: components/manage/Widgets/ImageSizeWidget +msgid "Medium" +msgstr "中等" + +# defaultMessage: Membership updated +#: helpers/MessageLabels/MessageLabels +msgid "Membership updated" +msgstr "成员身份更新" + +# defaultMessage: Message +#: components/theme/ContactForm/ContactForm +msgid "Message" +msgstr "消息" + +# defaultMessage: Minimum length is {len}. +#: components/manage/Form/ModalForm helpers/MessageLabels/MessageLabels +msgid "Minimum length is {len}." +msgstr "最短长度为{len}。" + +# defaultMessage: Minimum value is {len}. +#: helpers/MessageLabels/MessageLabels +msgid "Minimum value is {len}." +msgstr "最小值是{len}。" + +# defaultMessage: Moderate Comments +#: components/manage/Controlpanels/Controlpanels +msgid "Moderate Comments" +msgstr "审核评论" + +# defaultMessage: Moderate comments +#: components/manage/Controlpanels/ModerateComments +msgid "Moderate comments" +msgstr "审核评论" + +# defaultMessage: Monday and Friday +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Monday and Friday" +msgstr "周一和周五" + +# defaultMessage: Day +#: components/manage/Widgets/RecurrenceWidget/ByMonthDayField +msgid "Month day" +msgstr "月日" + +# defaultMessage: Monthly +#: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +msgid "Monthly" +msgstr "每月" + +# defaultMessage: More +#: components/manage/Toolbar/Toolbar +msgid "More" +msgstr "更多" + +# defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. +#: components/manage/Controlpanels/UpgradeControlPanel +msgid "More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide." +msgstr "更多关于升级步骤的信息可以在升级指南中的plone.org文档部分找到。" + +# defaultMessage: Mosaic layout +#: config/Views +msgid "Mosaic layout" +msgstr "" + +# defaultMessage: Move down +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Move down" +msgstr "下移" + +# defaultMessage: Move to bottom of folder +#: components/manage/Contents/ContentsItem +msgid "Move to bottom of folder" +msgstr "移动至文件夹底部" + +# defaultMessage: Move to top of folder +#: components/manage/Contents/ContentsItem +msgid "Move to top of folder" +msgstr "移动至文件夹顶部" + +# defaultMessage: Move up +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Move up" +msgstr "上移" + +# defaultMessage: Multiple choices? +#: components/manage/Blocks/Search/schema +msgid "Multiple choices?" +msgstr "多选?" + +# defaultMessage: My email is +#: components/theme/PasswordReset/PasswordReset +msgid "My email is" +msgstr "我的邮箱是" + +# defaultMessage: My username is +#: components/theme/PasswordReset/PasswordReset +msgid "My username is" +msgstr "我的用户名是" + +# defaultMessage: Name +#: components/manage/Sharing/Sharing components/theme/ContactForm/ContactForm +msgid "Name" +msgstr "名字" + +# defaultMessage: Narrow +#: components/manage/Widgets/AlignWidget +msgid "Narrow" +msgstr "" + +# defaultMessage: Navigate back +#: error +msgid "Navigate back" +msgstr "导航返回" + +# defaultMessage: Navigation +#: components/theme/Navigation/ContextNavigation +msgid "Navigation" +msgstr "导航" + +# defaultMessage: New password +#: components/manage/Preferences/ChangePassword +#: components/theme/PasswordReset/PasswordReset +msgid "New password" +msgstr "新的密码" + +# defaultMessage: News Item +#: components/manage/Toolbar/Toolbar +msgid "News Item" +msgstr "新闻" + +# defaultMessage: News item view +#: config/Views +msgid "News item view" +msgstr "新闻视图" + +# defaultMessage: No +#: components/manage/Contents/ContentsItem +#: components/manage/Contents/ContentsPropertiesModal +#: components/manage/Controlpanels/ContentTypes +msgid "No" +msgstr "否" + +# defaultMessage: No transactions found +#: components/manage/Controlpanels/UndoControlpanel +msgid "No Transactions Found" +msgstr "" + +# defaultMessage: No transactions selected +#: components/manage/Controlpanels/UndoControlpanel +msgid "No Transactions Selected" +msgstr "" + +# defaultMessage: No transactions selected to do undo +#: components/manage/Controlpanels/UndoControlpanel +msgid "No Transactions Selected To Do Undo" +msgstr "" + +# defaultMessage: No Video selected +#: components/manage/Blocks/Video/VideoSidebar +msgid "No Video selected" +msgstr "未选择视频" + +# defaultMessage: No addons found +#: components/manage/Controlpanels/VersionOverview +msgid "No addons found" +msgstr "没有找到附件" + +# defaultMessage: There is no connection to the server, due to a timeout o no network connection. +#: components/theme/RequestTimeout/RequestTimeout +msgid "No connection to the server" +msgstr "与服务器无连接" + +# defaultMessage: No image selected +#: components/manage/Blocks/Image/ImageSidebar +msgid "No image selected" +msgstr "未选择图像" + +# defaultMessage: No image set in Lead Image content field +#: components/manage/Blocks/LeadImage/LeadImageSidebar +msgid "No image set in Lead Image content field" +msgstr "" + +# defaultMessage: No image set in image content field +#: components/manage/Blocks/LeadImage/LeadImageSidebar +msgid "No image set in image content field" +msgstr "在图像内容字段中没有设置图像" + +# defaultMessage: No items found in this container. +#: components/manage/Blocks/Listing/ListingBody +msgid "No items found in this container." +msgstr "在此容器中没有发现条目。" + +# defaultMessage: No items selected +#: components/manage/Widgets/ObjectBrowserWidget +msgid "No items selected" +msgstr "未选择条目" + +# defaultMessage: No map selected +#: components/manage/Blocks/Maps/MapsSidebar +msgid "No map selected" +msgstr "未选择镜像" + +# defaultMessage: No occurences set +#: components/manage/Widgets/RecurrenceWidget/Occurences +msgid "No occurences set" +msgstr "" + +# defaultMessage: No options +#: components/manage/Widgets/ArrayWidget +#: components/manage/Widgets/SelectAutoComplete +#: components/manage/Widgets/SelectWidget components/manage/Widgets/TokenWidget +msgid "No options" +msgstr "没有选项" + +# defaultMessage: No results found +#: components/manage/BlockChooser/BlockChooser components/theme/Search/Search +msgid "No results found" +msgstr "未找到结果" + +# defaultMessage: No results found. +#: components/manage/Blocks/Listing/ListingBody +#: components/manage/Widgets/ReferenceWidget +msgid "No results found." +msgstr "未找到结果。" + +# defaultMessage: No selection +#: components/manage/Blocks/Search/components/SortOn +#: components/manage/Widgets/QuerySortOnWidget +#: components/manage/Widgets/QuerystringWidget +msgid "No selection" +msgstr "没有选择" + +# defaultMessage: This addon does not provide an uninstall profile. +#: components/manage/Controlpanels/AddonsControlpanel +msgid "No uninstall profile" +msgstr "没有卸载配置文件" + +# defaultMessage: No user found +#: helpers/MessageLabels/MessageLabels +msgid "No user found" +msgstr "未找到用户" + +# defaultMessage: No value +#: components/manage/Widgets/ArrayWidget +#: components/manage/Widgets/ReferenceWidget +#: components/manage/Widgets/SelectUtils components/manage/Widgets/SelectWidget +msgid "No value" +msgstr "没有值" + +# defaultMessage: No workflow +#: components/manage/Workflow/Workflow +msgid "No workflow" +msgstr "没有工作流" + +# defaultMessage: None +#: components/manage/Contents/Contents components/manage/Contents/ContentsItem +msgid "None" +msgstr "" + +# defaultMessage: Note +#: components/manage/Controlpanels/UndoControlpanel +msgid "Note" +msgstr "" + +# defaultMessage: Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group. +#: components/manage/Controlpanels/Users/UsersControlpanel +msgid "Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group." +msgstr "请注意这里设置的角色将直接应用于用户。符号{plone_svg}表示从组成员继承的角色。" + +# defaultMessage: Number of active objects +#: components/manage/Controlpanels/DatabaseInformation +msgid "Number of active objects" +msgstr "活动对象的数量" + +# defaultMessage: Object Size +#: components/manage/Contents/Contents +msgid "Object Size" +msgstr "对象大小" + +# defaultMessage: occurrence(s) +#: components/manage/Widgets/RecurrenceWidget/EndField +msgid "Occurences" +msgstr "" + +# defaultMessage: Ok +#: components/manage/Delete/Delete +msgid "Ok" +msgstr "" + +# defaultMessage: Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed. +#: components/manage/Widgets/IdWidget +msgid "Only lowercase letters (a-z) without accents, numbers (0-9), and the characters \"-\", \"_\", and \".\" are allowed." +msgstr "只允许不带读音号的小写字母(a-z),数字(0-9),和符号:_,-,.。" + +# defaultMessage: Open in a new tab +#: components/manage/Blocks/Image/schema +#: components/manage/Blocks/LeadImage/LeadImageSidebar +msgid "Open in a new tab" +msgstr "在新标签中打开" + +# defaultMessage: Open menu +#: components/theme/Navigation/Navigation +msgid "Open menu" +msgstr "打开菜单" + +# defaultMessage: Open object browser +#: components/manage/Widgets/ObjectBrowserWidget +msgid "Open object browser" +msgstr "打开目标浏览器" + +# defaultMessage: Origin +#: components/manage/Blocks/LeadImage/LeadImageSidebar +msgid "Origin" +msgstr "原始" + +# defaultMessage: Page +#: components/manage/Toolbar/Toolbar +msgid "Page" +msgstr "页面" + +# defaultMessage: Parent fieldset +#: components/manage/Widgets/SchemaWidget +msgid "Parent fieldset" +msgstr "父字段集" + +# defaultMessage: Password +#: components/theme/Login/Login helpers/MessageLabels/MessageLabels +msgid "Password" +msgstr "密码" + +# defaultMessage: Password reset +#: components/theme/PasswordReset/PasswordReset +#: components/theme/PasswordReset/RequestPasswordReset +msgid "Password reset" +msgstr "重置密码" + +# defaultMessage: Passwords do not match. +#: components/theme/PasswordReset/PasswordReset +msgid "Passwords do not match." +msgstr "密码不匹配。" + +# defaultMessage: Paste +#: components/manage/Actions/Actions components/manage/Contents/Contents +msgid "Paste" +msgstr "粘贴" + +# defaultMessage: undefined +#: helpers/MessageLabels/MessageLabels +msgid "Paste blocks" +msgstr "粘贴块" + +# defaultMessage: Perform the following actions: +#: components/manage/Controlpanels/Rules/ConfigureRule +msgid "Perform the following actions:" +msgstr "执行下面的操作:" + +# defaultMessage: Permissions have been updated successfully +#: components/manage/Sharing/Sharing +msgid "Permissions have been updated successfully" +msgstr "权限更新成功" + +# defaultMessage: Permissions updated +#: components/manage/Sharing/Sharing +msgid "Permissions updated" +msgstr "权限更新" + +# defaultMessage: Personal Information +#: components/manage/Toolbar/Toolbar +msgid "Personal Information" +msgstr "个人信息" + +# defaultMessage: Personal Preferences +#: components/manage/Preferences/PersonalPreferences +#: components/manage/Toolbar/Toolbar +msgid "Personal Preferences" +msgstr "个人偏好" + +# defaultMessage: Personal tools +#: components/manage/Toolbar/More components/manage/Toolbar/Toolbar +msgid "Personal tools" +msgstr "个人工具" + +# defaultMessage: Persons responsible for creating the content of this item. Please enter a list of user names, one per line. The principal creator should come first. +#: components/manage/Contents/ContentsPropertiesModal +msgid "Persons responsible for creating the content of this item. Please enter a list of user names, one per line. The principal creator should come first." +msgstr "负责创建此条目内容的人员。请输入用户名列表,每行一个。主要创建者应放在首位。" + +# defaultMessage: Please continue with the upgrade. +#: components/manage/Controlpanels/Controlpanels +msgid "Please continue with the upgrade." +msgstr "请继续升级。" + +# defaultMessage: Please ensure you have a backup of your site before performing the upgrade. +#: components/manage/Controlpanels/UpgradeControlPanel +msgid "Please ensure you have a backup of your site before performing the upgrade." +msgstr "在执行升级前,请确定您已对网站进行备份。" + +# defaultMessage: Please enter a valid URL by deleting the block and adding a new video block. +#: components/manage/Blocks/Video/Body +msgid "Please enter a valid URL by deleting the block and adding a new video block." +msgstr "" + +# defaultMessage: Please enter the Embed Code provided by Google Maps -> Share -> Embed map. It should contain the -## Presentations at the last two PloneConf's +## Presentations at Plone Conferences (PloneConf) and other events -In recent years the new Volto frontend for Plone has been presented in more and more talks from +In recent years the react based Volto frontend for Plone has been presented in more and more talks at our yearly Conferences: -- [PloneConf 2021 Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkQDLQinBwdEXpebDTQCwdGi) +### PloneConf 2022 -- [PloneConf 2020 Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkTJPayNdKIZ8lLDm5RVOLV3) +[PloneConf 2022 full Playlist on Youtube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkQxqQcCZeJefMC8XlA_qv3Z) +Plone 6 site presentations: -## PloneConf 2019 +- [Rai Way: Plone6 supporting the world of italian information, sports and entertainment](https://www.youtube.com/watch?v=hHHGlSjf5O4&list=PLGN9BI-OAQkQxqQcCZeJefMC8XlA_qv3Z) +- [How Plone Powers Hundreds of Websites at one of the Largest Research Institutions in Europe](https://www.youtube.com/watch?v=bxWt-GEmPcc&list=PLGN9BI-OAQkQxqQcCZeJefMC8XlA_qv3Z) -PloneConf 2019 and other events have several Volto-relevant presentations, but not stored in a -single playlist: -### Howtos +Developer/intergrator talks: -- [Rob Gietema - How to create your own Volto site!](https://www.youtube.com/watch?v=3QLN8tsjjf4) -- [Rodrigo Ferreira de Souza - Data migration to Plone 5.2 and Volto](https://www.youtube.com/watch?v=kb9SEsnllqE) +- [Anatomy of a Volto project](https://www.youtube.com/watch?v=JtNufyFlgc8&list=PLGN9BI-OAQkQxqQcCZeJefMC8XlA_qv3Z) +- [Creating a Volto Theme](https://www.youtube.com/watch?v=AMHN74Jr27Y&list=PLGN9BI-OAQkQxqQcCZeJefMC8XlA_qv3Z) +- [A Deep Dive Into Internals Of Volto](https://www.youtube.com/watch?v=sMeTDRgp3uI&list=PLGN9BI-OAQkQxqQcCZeJefMC8XlA_qv3Z) +- [DevOps Bird's Eye View on the Plone 6 Backend](https://www.youtube.com/watch?v=L5PvGwWC9P4&list=PLGN9BI-OAQkQxqQcCZeJefMC8XlA_qv3Z) -### Panels and discussions +## Previous PloneConfs -- [Timo Stollenwerk - Breaking New Grounds](https://www.youtube.com/watch?v=9nRxgeCuIDs) -- [Panel - Ask me anything on Volto](https://www.youtube.com/watch?v=jwbpXJlDVOs) -- [Luca Pisani - Plone and React.js: An interview to Volto](https://www.youtube.com/watch?v=JZFUOG843no) -- [Victor Fernandez de Alba - Plone Beyond 2020: Jump into Volto today!](https://www.youtube.com/watch?v=8QrGOgXo1Js) -- [Nicola Zambello - A Volto story: building a website by prototyping](https://www.youtube.com/watch?v=xtxJURICkWc) +- [PloneConf 2021 full Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkQDLQinBwdEXpebDTQCwdGi) +- [PloneConf 2020 full Playlist on YouTube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkTJPayNdKIZ8lLDm5RVOLV3) + + +## World Plone Day 2022 + +World Plone Day is a 24-hour streaming event, with the goal was to promote and educate the public about the benefits of using Plone and of being part of the Plone community. [Full World Plone Day 2022 playlist on Youtube](https://www.youtube.com/playlist?list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL) + +- [Plone 6 Volto's Seamless Mode](https://www.youtube.com/watch?v=Mj8pHRBls-w&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL) +- [Volto add ons separator and carousel](https://www.youtube.com/watch?v=eyTMI5TYcVg&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL) +- [Weekly Volto Live – Retrospective](https://www.youtube.com/watch?v=WT6OjkSrB20&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL) +- [Migrating from Classic to Volto](https://www.youtube.com/watch?v=09fg456T90s&list=PLGN9BI-OAQkQmEqf6O8jeyoFY1b2hD1uL) From cd7eaeb269b164af2f74d3bf227eebf178e16d99 Mon Sep 17 00:00:00 2001 From: Tiberiu Ichim Date: Mon, 12 Dec 2022 11:44:31 +0200 Subject: [PATCH 119/326] Introduce options to allow a deserializer that doesn't collapse whitespace (#4082) --- cypress/tests/coresandbox/fields.js | 44 ++++++++++++- news/4082.bugfix | 1 + .../src/components/Blocks/TestBlock/View.jsx | 7 ++- .../src/components/Blocks/TestBlock/schema.js | 5 ++ .../volto-slate/src/editor/deserialize.js | 61 +++++++++++-------- .../src/widgets/HtmlSlateWidget.jsx | 2 +- 6 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 news/4082.bugfix diff --git a/cypress/tests/coresandbox/fields.js b/cypress/tests/coresandbox/fields.js index 007603e14e..ddf4230bd2 100644 --- a/cypress/tests/coresandbox/fields.js +++ b/cypress/tests/coresandbox/fields.js @@ -1,5 +1,5 @@ context('Special fields Acceptance Tests', () => { - describe.only('Form with default values', () => { + describe('Form with default values', () => { beforeEach(() => { // given a logged in editor and a page in edit mode cy.visit('/'); @@ -62,6 +62,45 @@ context('Special fields Acceptance Tests', () => { }); }); + describe('HTML Richtext Widget', () => { + beforeEach(() => { + // given a logged in editor and a page in edit mode + cy.visit('/'); + cy.autologin(); + cy.createContent({ + contentType: 'Document', + contentId: 'document', + contentTitle: 'Test document', + }); + cy.visit('/document'); + cy.waitForResourceToLoad('@navigation'); + cy.waitForResourceToLoad('@breadcrumbs'); + cy.waitForResourceToLoad('@actions'); + cy.waitForResourceToLoad('@types'); + cy.waitForResourceToLoad('document'); + cy.navigate('/document/edit'); + cy.getSlateTitle(); + }); + + it('Handles whitespaces properly', () => { + cy.intercept('PATCH', '/**/document').as('save'); + cy.getSlate().click(); + cy.get('.button .block-add-button').click({ force: true }); + cy.get('.blocks-chooser .mostUsed .button.testBlock').click(); + cy.get('#fieldset-default-field-label-html').click(); + cy.get('.slate_wysiwyg_box [contenteditable=true]').type( + ' hello world ', + ); + cy.get('#toolbar-save').click(); + cy.wait('@save'); + + cy.get('.test-block').should( + 'contain.text', + '

    hello world

    ', + ); + }); + }); + describe('ObjectListWidget', () => { beforeEach(() => { // given a logged in editor and a page in edit mode @@ -116,6 +155,7 @@ context('Special fields Acceptance Tests', () => { cy.findAllByText('Item #3').should('have.length', 0); }); }); + describe('Variation field', () => { beforeEach(() => { // given a logged in editor and a page in edit mode @@ -146,6 +186,7 @@ context('Special fields Acceptance Tests', () => { cy.findByText('Custom'); }); }); + describe('ObjectBrowserWidget', () => { beforeEach(() => { // given a logged in editor and a page in edit mode @@ -171,6 +212,7 @@ context('Special fields Acceptance Tests', () => { cy.navigate('/document/edit'); cy.getSlateTitle(); }); + it('As editor I can add a block with an objetBrowserWidget and the context path is preserved', function () { cy.getSlate().click(); cy.get('.button .block-add-button').click({ force: true }); diff --git a/news/4082.bugfix b/news/4082.bugfix new file mode 100644 index 0000000000..261441a8c2 --- /dev/null +++ b/news/4082.bugfix @@ -0,0 +1 @@ +Properly handle whitespace in HTML (richtext) slate-based widget @tiberiuichim diff --git a/packages/coresandbox/src/components/Blocks/TestBlock/View.jsx b/packages/coresandbox/src/components/Blocks/TestBlock/View.jsx index cd012ad748..a8ad1fe8f3 100644 --- a/packages/coresandbox/src/components/Blocks/TestBlock/View.jsx +++ b/packages/coresandbox/src/components/Blocks/TestBlock/View.jsx @@ -1,7 +1,12 @@ import React from 'react'; const TestBlockView = (props) => { - return
    Test Block
    ; + return ( +
    +
    Test Block
    +

    {JSON.stringify(props.data)}

    +
    + ); }; export default TestBlockView; diff --git a/packages/coresandbox/src/components/Blocks/TestBlock/schema.js b/packages/coresandbox/src/components/Blocks/TestBlock/schema.js index 3f059f8153..8aa4ce8a41 100644 --- a/packages/coresandbox/src/components/Blocks/TestBlock/schema.js +++ b/packages/coresandbox/src/components/Blocks/TestBlock/schema.js @@ -96,6 +96,7 @@ export const SliderSchema = (props) => ({ id: 'default', title: 'Default', fields: [ + 'html', 'slides', 'fieldAfterObjectList', 'href', @@ -149,6 +150,10 @@ export const SliderSchema = (props) => ({ required: [], }, }, + html: { + title: 'HTML', + widget: 'richtext', + }, }, required: [], }); diff --git a/packages/volto-slate/src/editor/deserialize.js b/packages/volto-slate/src/editor/deserialize.js index bdf1aa988b..6f57132fb6 100644 --- a/packages/volto-slate/src/editor/deserialize.js +++ b/packages/volto-slate/src/editor/deserialize.js @@ -16,13 +16,19 @@ import { collapseInlineSpace } from './utils'; * * See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace */ -export const deserialize = (editor, el) => { +export const deserialize = ( + editor, + el, + options = { collapseWhitespace: true }, +) => { const { htmlTagsToSlate } = editor; if (el.nodeType === COMMENT) { return null; } else if (el.nodeType === TEXT_NODE) { - const text = collapseInlineSpace(el); + const text = options.collapseWhitespace + ? collapseInlineSpace(el) + : el.textContent; return text ? { text, @@ -36,33 +42,39 @@ export const deserialize = (editor, el) => { } if (el.getAttribute('data-slate-data')) { - return typeDeserialize(editor, el); + return typeDeserialize(editor, el, options); } const { nodeName } = el; if (htmlTagsToSlate[nodeName]) { - return htmlTagsToSlate[nodeName](editor, el); + return htmlTagsToSlate[nodeName](editor, el, options); } // fallback deserializer, all unknown elements are "stripped" - return deserializeChildren(el, editor); + return deserializeChildren(el, editor, options); }; -export const typeDeserialize = (editor, el) => { +export const typeDeserialize = (editor, el, options) => { const jsData = el.getAttribute('data-slate-data'); const { type, data } = JSON.parse(jsData); - return jsx('element', { type, data }, deserializeChildren(el, editor)); + return jsx( + 'element', + { type, data }, + deserializeChildren(el, editor, options), + ); }; -export const deserializeChildren = (parent, editor) => +export const deserializeChildren = (parent, editor, options) => Array.from(parent.childNodes) - .map((el) => deserialize(editor, el)) + .map((el) => deserialize(editor, el, options)) .flat(); -export const blockTagDeserializer = (tagname) => (editor, el) => { +export const blockTagDeserializer = (tagname) => (editor, el, options) => { // if (tagname === 'h2') debugger; - let children = deserializeChildren(el, editor).filter((n) => n !== null); + let children = deserializeChildren(el, editor, options).filter( + (n) => n !== null, + ); if ( [TD, TH].includes(tagname) && @@ -83,15 +95,16 @@ export const blockTagDeserializer = (tagname) => (editor, el) => { children = [{ text: '' }]; } + console.log('children', children); return jsx('element', { type: tagname }, children); }; -export const bodyTagDeserializer = (editor, el) => { - return jsx('fragment', {}, deserializeChildren(el, editor)); +export const bodyTagDeserializer = (editor, el, options) => { + return jsx('fragment', {}, deserializeChildren(el, editor, options)); }; -export const inlineTagDeserializer = (attrs) => (editor, el) => { - return deserializeChildren(el, editor).map((child) => { +export const inlineTagDeserializer = (attrs) => (editor, el, options) => { + return deserializeChildren(el, editor, options).map((child) => { const res = Text.isText(child) || typeof child === 'string' ? jsx('text', attrs, child) @@ -103,7 +116,7 @@ export const inlineTagDeserializer = (attrs) => (editor, el) => { }); }; -export const spanTagDeserializer = (editor, el) => { +export const spanTagDeserializer = (editor, el, options) => { const style = el.getAttribute('style') || ''; let children = el.childNodes; @@ -115,7 +128,7 @@ export const spanTagDeserializer = (editor, el) => { ) { return jsx('text', {}, ' '); } - children = deserializeChildren(el, editor); + children = deserializeChildren(el, editor, options); // whitespace is replaced by deserialize() with null; children = children.map((c) => (c === null ? '' : c)); @@ -144,18 +157,18 @@ export const spanTagDeserializer = (editor, el) => { return res; }; -export const bTagDeserializer = (editor, el) => { +export const bTagDeserializer = (editor, el, options) => { // Google Docs does weird things with tag return (el.getAttribute('id') || '').indexOf('docs-internal-guid') > -1 - ? deserializeChildren(el, editor) - : jsx('element', { type: 'b' }, deserializeChildren(el, editor)); + ? deserializeChildren(el, editor, options) + : jsx('element', { type: 'b' }, deserializeChildren(el, editor, options)); }; -export const codeTagDeserializer = (editor, el) => { +export const codeTagDeserializer = (editor, el, options) => { return jsx('element', { type: 'code' }, el.textContent); }; -export const preTagDeserializer = (editor, el) => { +export const preTagDeserializer = (editor, el, options) => { // Based on Slate example implementation. Replaces
     tags with .
       // Comment: I don't know how good of an idea is this. I'd rather have two
       // separate formats: "preserve whitespace" and "code". This feels like a hack
    @@ -164,10 +177,10 @@ export const preTagDeserializer = (editor, el) => {
     
       if (el.childNodes[0] && el.childNodes[0].nodeName === 'CODE') {
         parent = el.childNodes[0];
    -    return codeTagDeserializer(editor, parent);
    +    return codeTagDeserializer(editor, parent, options);
       }
     
    -  return blockTagDeserializer(nodeName)(editor, parent);
    +  return blockTagDeserializer(nodeName)(editor, parent, options);
     };
     
     export default deserialize;
    diff --git a/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx b/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx
    index 92ca1b65a6..a09859c6da 100644
    --- a/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx
    +++ b/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx
    @@ -80,7 +80,7 @@ const HtmlSlateWidget = (props) => {
             parsed.getElementsByTagName('google-sheets-html-origin').length > 0
               ? parsed.querySelector('google-sheets-html-origin > table')
               : parsed.body;
    -      let data = deserialize(editor, body);
    +      let data = deserialize(editor, body, { collapseWhitespace: false });
           data = normalizeExternalData(editor, data);
     
           // editor.children = data;
    
    From de1215f6dba098e2b666324a90ede634eb0595a2 Mon Sep 17 00:00:00 2001
    From: Victor Fernandez de Alba 
    Date: Mon, 12 Dec 2022 10:47:55 +0100
    Subject: [PATCH 120/326] Update locales and changelog missing for 4009
    
    ---
     locales/zh_CN/LC_MESSAGES/volto.po | 1890 +++++++++++++++-------------
     news/4009.bugfix                   |    1 +
     2 files changed, 994 insertions(+), 897 deletions(-)
    
    diff --git a/locales/zh_CN/LC_MESSAGES/volto.po b/locales/zh_CN/LC_MESSAGES/volto.po
    index 499d559cbd..f2045d7745 100644
    --- a/locales/zh_CN/LC_MESSAGES/volto.po
    +++ b/locales/zh_CN/LC_MESSAGES/volto.po
    @@ -5,398 +5,405 @@ msgstr ""
     "POT-Creation-Date: \n"
     "PO-Revision-Date: \n"
     "Last-Translator: \n"
    -"Language-Team: \n"
     "Language: zh_CN\n"
    -"MIME-Version: 1.0\n"
    +"Language-Team: \n"
     "Content-Type: text/plain; charset=UTF-8\n"
     "Content-Transfer-Encoding: 8bit\n"
     "Plural-Forms: nplurals=1; plural=0;\n"
    +"MIME-Version: 1.0\n"
     "Language-Code: zh_CN\n"
     "Language-Name: Chinese\n"
     "Preferred-Encodings: utf-8\n"
     "X-Is-Fallback-For: zh-cn\n"
     "X-Generator: Poedit 3.2\n"
     
    -# defaultMessage: 

    Add some HTML here

    #: components/manage/Blocks/HTML/Edit +# defaultMessage:

    Add some HTML here

    msgid "

    Add some HTML here

    " msgstr "在此处添加一些HTML" -# defaultMessage: Account Registration Completed #: components/theme/Register/Register +# defaultMessage: Account Registration Completed msgid "Account Registration Completed" msgstr "账号注册完成" -# defaultMessage: Account activation completed #: components/theme/PasswordReset/PasswordReset +# defaultMessage: Account activation completed msgid "Account activation completed" msgstr "账号激活完成" -# defaultMessage: Action #: components/manage/Controlpanels/ModerateComments +# defaultMessage: Action msgid "Action" msgstr "操作" -# defaultMessage: Action changed #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Action changed msgid "Action changed" msgstr "操作改变" -# defaultMessage: Action: #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Action: msgid "Action: " msgstr "操作:" -# defaultMessage: Actions -#: components/manage/Actions/Actions components/manage/Contents/Contents +#: components/manage/Actions/Actions +#: components/manage/Contents/Contents #: components/manage/Controlpanels/ContentTypes #: components/manage/Controlpanels/Groups/GroupsControlpanel #: components/manage/Controlpanels/Rules/Rules #: components/manage/Controlpanels/Users/UsersControlpanel +# defaultMessage: Actions msgid "Actions" msgstr "操作" -# defaultMessage: Activate and deactivate add-ons in the lists below. #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Activate and deactivate add-ons in the lists below. msgid "Activate and deactivate" msgstr "启用或者禁用" -# defaultMessage: Active #: components/manage/Rules/Rules +# defaultMessage: Active msgid "Active" msgstr "已启用" -# defaultMessage: Active content rules in this Page #: components/manage/Rules/Rules +# defaultMessage: Active content rules in this Page msgid "Active content rules in this Page" msgstr "在此页面中活动的内容规则" -# defaultMessage: Add -#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases +#: components/manage/Aliases/Aliases +#: components/manage/Controlpanels/Aliases #: components/manage/Controlpanels/ContentTypes #: components/manage/Controlpanels/Rules/ConfigureRule -#: components/manage/Rules/Rules components/manage/Toolbar/Toolbar -#: components/manage/Widgets/SchemaWidget helpers/MessageLabels/MessageLabels +#: components/manage/Rules/Rules +#: components/manage/Toolbar/Toolbar +#: components/manage/Widgets/SchemaWidget +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Add msgid "Add" msgstr "添加" -# defaultMessage: Add #: components/manage/Widgets/ObjectListWidget +# defaultMessage: Add msgid "Add (object list)" msgstr "添加(对象列表)" -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "添加附件" -# defaultMessage: Add Content… #: components/manage/Toolbar/Types +# defaultMessage: Add Content… msgid "Add Content" msgstr "添加内容" -# defaultMessage: Add Content Rule #: components/manage/Controlpanels/Rules/AddRule +# defaultMessage: Add Content Rule msgid "Add Content Rule" msgstr "添加内容规则" -# defaultMessage: Add Rule #: components/manage/Controlpanels/Rules/AddRule +# defaultMessage: Add Rule msgid "Add Rule" msgstr "添加规则" -# defaultMessage: Add Translation… #: components/manage/Toolbar/Types +# defaultMessage: Add Translation… msgid "Add Translation…" msgstr "添加翻译" -# defaultMessage: Add User #: helpers/MessageLabels/MessageLabels +# defaultMessage: Add User msgid "Add User" msgstr "添加用户" -# defaultMessage: Add a description… #: components/manage/Blocks/Description/Edit +# defaultMessage: Add a description… msgid "Add a description…" msgstr "添加描述" -# defaultMessage: Add a new alternative url #: components/manage/Aliases/Aliases +# defaultMessage: Add a new alternative url msgid "Add a new alternative url" msgstr "添加新的替代 URL" -# defaultMessage: Action added #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Action added msgid "Add action" msgstr "添加操作" -# defaultMessage: Add block #: components/manage/BlockChooser/BlockChooserButton +# defaultMessage: Add block msgid "Add block" msgstr "添加block" -# defaultMessage: Add block… #: helpers/MessageLabels/MessageLabels +# defaultMessage: Add block… msgid "Add block…" msgstr "添加块..." -# defaultMessage: Condition added #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Condition added msgid "Add condition" msgstr "添加条件" -# defaultMessage: Add content rule #: components/manage/Controlpanels/Rules/Rules +# defaultMessage: Add content rule msgid "Add content rule" msgstr "添加内容规则" -# defaultMessage: Add criteria #: components/manage/Widgets/QueryWidget +# defaultMessage: Add criteria msgid "Add criteria" msgstr "添加标准" -# defaultMessage: Add date #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: Add date msgid "Add date" msgstr "添加日期" -# defaultMessage: Add field #: components/manage/Widgets/SchemaWidget +# defaultMessage: Add field msgid "Add field" msgstr "添加字段" -# defaultMessage: Add fieldset #: components/manage/Widgets/SchemaWidget +# defaultMessage: Add fieldset msgid "Add fieldset" msgstr "添加字段集" -# defaultMessage: Add group #: helpers/MessageLabels/MessageLabels +# defaultMessage: Add group msgid "Add group" msgstr "添加组" -# defaultMessage: Add new content type #: components/manage/Controlpanels/ContentTypes +# defaultMessage: Add new content type msgid "Add new content type" msgstr "添加新的内容类型" -# defaultMessage: Add new group #: helpers/MessageLabels/MessageLabels +# defaultMessage: Add new group msgid "Add new group" msgstr "添加新组" -# defaultMessage: Add new user #: helpers/MessageLabels/MessageLabels +# defaultMessage: Add new user msgid "Add new user" msgstr "添加新用户" -# defaultMessage: Add to Groups #: helpers/MessageLabels/MessageLabels +# defaultMessage: Add to Groups msgid "Add to Groups" msgstr "添加到组" -# defaultMessage: Add users to group #: helpers/MessageLabels/MessageLabels +# defaultMessage: Add users to group msgid "Add users to group" msgstr "添加用户到组" -# defaultMessage: Add term #: components/manage/Widgets/VocabularyTermsWidget +# defaultMessage: Add term msgid "Add vocabulary term" msgstr "添加词汇" -# defaultMessage: Add {type} #: components/manage/Add/Add +# defaultMessage: Add {type} msgid "Add {type}" msgstr "添加{type}" -# defaultMessage: Add-Ons #: components/manage/Controlpanels/Controlpanels +# defaultMessage: Add-Ons msgid "Add-Ons" msgstr "附加组件" -# defaultMessage: Add-on Configuration #: components/manage/Controlpanels/Controlpanels +# defaultMessage: Add-on Configuration msgid "Add-on Configuration" msgstr "附加组件配置" -# defaultMessage: Add-ons #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Add-ons msgid "Add-ons" msgstr "附加组件" -# defaultMessage: Add-ons Settings #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Add-ons Settings msgid "Add-ons Settings" msgstr "附加组件设置" -# defaultMessage: Added #: components/manage/Rules/Rules +# defaultMessage: Added msgid "Added" msgstr "已添加" -# defaultMessage: Additional date #: components/manage/Widgets/RecurrenceWidget/Occurences +# defaultMessage: Additional date msgid "Additional date" msgstr "附加日期" -# defaultMessage: Addon could not be installed #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Addon could not be installed msgid "Addon could not be installed" msgstr "无法安装附件" -# defaultMessage: Addon could not be uninstalled #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Addon could not be uninstalled msgid "Addon could not be uninstalled" msgstr "无法卸载附件" -# defaultMessage: Addon could not be upgraded #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Addon could not be upgraded msgid "Addon could not be upgraded" msgstr "无法升级附件" -# defaultMessage: Addon installed succesfuly #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Addon installed succesfuly msgid "Addon installed succesfuly" msgstr "附件安装成功" -# defaultMessage: Addon uninstalled succesfuly #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Addon uninstalled succesfuly msgid "Addon uninstalled succesfuly" msgstr "附件卸载成功" -# defaultMessage: Addon upgraded succesfuly #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Addon upgraded succesfuly msgid "Addon upgraded succesfuly" msgstr "附件升级成功" -# defaultMessage: Album view #: config/Views +# defaultMessage: Album view msgid "Album view" msgstr "相册视图" -# defaultMessage: Alias #: components/manage/Controlpanels/Aliases +# defaultMessage: Alias msgid "Alias" msgstr "别名" +#: components/manage/Aliases/Aliases +#: components/manage/Controlpanels/Aliases # defaultMessage: Alias has been added -#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases msgid "Alias has been added" msgstr "别名已添加" -# defaultMessage: Alignment #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar -#: components/manage/Blocks/Maps/schema components/manage/Blocks/Video/schema +#: components/manage/Blocks/Maps/schema +#: components/manage/Blocks/Video/schema +# defaultMessage: Alignment msgid "Alignment" msgstr "对齐" -# defaultMessage: All #: components/manage/Contents/Contents +# defaultMessage: All msgid "All" msgstr "所有" -# defaultMessage: All content #: config/Views +# defaultMessage: All content msgid "All content" msgstr "所有内容" -# defaultMessage: All existing alternative urls for this site #: components/manage/Controlpanels/Aliases +# defaultMessage: All existing alternative urls for this site msgid "All existing alternative urls for this site" msgstr "此网站所有的替代 urls" -# defaultMessage: Alphabetically #: components/theme/Search/Search +# defaultMessage: Alphabetically msgid "Alphabetically" msgstr "按字母表顺序" -# defaultMessage: Alt text #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar #: components/manage/Blocks/Maps/schema +# defaultMessage: Alt text msgid "Alt text" msgstr "替换文本" -# defaultMessage: Leave empty if the image is purely decorative. #: components/manage/Blocks/Image/schema +# defaultMessage: Leave empty if the image is purely decorative. msgid "Alt text hint" msgstr "替换文本提示" -# defaultMessage: Describe the purpose of the image. #: components/manage/Blocks/Image/schema +# defaultMessage: Describe the purpose of the image. msgid "Alt text hint link text" msgstr "替换文本提示链接文本" -# defaultMessage: Alternative url path (Required) #: components/manage/Controlpanels/Aliases +# defaultMessage: Alternative url path (Required) msgid "Alternative url path (Required)" msgstr "替代 URL 路径(必选)" +#: components/manage/Aliases/Aliases +#: components/manage/Controlpanels/Aliases # defaultMessage: Alternative url path must start with a slash. -#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases msgid "Alternative url path must start with a slash." msgstr "替代 URL 路径必须以斜杠开头。" -# defaultMessage: Alternative url path → target url path (date and time of creation, manually created yes/no) #: components/manage/Controlpanels/Aliases +# defaultMessage: Alternative url path → target url path (date and time of creation, manually created yes/no) msgid "Alternative url path → target url path (date and time of creation, manually created yes/no)" msgstr "替代 URL 路径 → 目标 URL 路径(创建的日期和时间,手动创建 yes/no)" -# defaultMessage: Applied to subfolders #: components/manage/Rules/Rules +# defaultMessage: Applied to subfolders msgid "Applied to subfolders" msgstr "应用于子文件夹" -# defaultMessage: Applies to subfolders? #: components/manage/Rules/Rules +# defaultMessage: Applies to subfolders? msgid "Applies to subfolders?" msgstr "是否应用于子文件夹?" -# defaultMessage: Apply to subfolders #: components/manage/Rules/Rules +# defaultMessage: Apply to subfolders msgid "Apply to subfolders" msgstr "应用于子文件夹" -# defaultMessage: Apply working copy #: components/manage/Toolbar/More +# defaultMessage: Apply working copy msgid "Apply working copy" msgstr "申请工作副本" -# defaultMessage: Are you sure you want to delete this field? #: components/manage/Widgets/SchemaWidget +# defaultMessage: Are you sure you want to delete this field? msgid "Are you sure you want to delete this field?" msgstr "确定要删除此字段?" -# defaultMessage: Are you sure you want to delete this fieldset including all fields? #: components/manage/Widgets/SchemaWidget +# defaultMessage: Are you sure you want to delete this fieldset including all fields? msgid "Are you sure you want to delete this fieldset including all fields?" msgstr "确定要删除此包括所有字段的字段集?" -# defaultMessage: Ascending #: components/manage/Blocks/Search/components/SortOn #: components/manage/Contents/Contents +# defaultMessage: Ascending msgid "Ascending" msgstr "上升" -# defaultMessage: Assignments #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Assignments msgid "Assignments" msgstr "分配" -# defaultMessage: Available #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Available msgid "Available" msgstr "可用的" -# defaultMessage: Available content rules: #: components/manage/Rules/Rules +# defaultMessage: Available content rules: msgid "Available content rules:" msgstr "可用的内容规则" -# defaultMessage: Back -#: components/manage/Aliases/Aliases components/manage/Contents/Contents +#: components/manage/Aliases/Aliases +#: components/manage/Contents/Contents #: components/manage/Controlpanels/AddonsControlpanel #: components/manage/Controlpanels/Aliases #: components/manage/Controlpanels/ContentType @@ -412,453 +419,465 @@ msgstr "可用的内容规则" #: components/manage/Controlpanels/Rules/Rules #: components/manage/Controlpanels/UndoControlpanel #: components/manage/Controlpanels/UpgradeControlPanel -#: components/manage/Diff/Diff components/manage/History/History +#: components/manage/Diff/Diff +#: components/manage/History/History #: components/manage/Multilingual/ManageTranslations #: components/manage/Preferences/ChangePassword #: components/manage/Preferences/PersonalPreferences -#: components/manage/Rules/Rules components/manage/Sharing/Sharing +#: components/manage/Rules/Rules +#: components/manage/Sharing/Sharing #: components/manage/Sidebar/ObjectBrowserBody -#: components/manage/Toolbar/PersonalTools components/manage/Toolbar/Toolbar -#: components/theme/ContactForm/ContactForm helpers/MessageLabels/MessageLabels +#: components/manage/Toolbar/PersonalTools +#: components/manage/Toolbar/Toolbar +#: components/theme/ContactForm/ContactForm +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Back msgid "Back" msgstr "返回" -# defaultMessage: Base #: components/manage/Diff/Diff +# defaultMessage: Base msgid "Base" msgstr "基本" -# defaultMessage: Base search query #: components/manage/Blocks/Search/schema +# defaultMessage: Base search query msgid "Base search query" msgstr "基本搜索查询" -# defaultMessage: Block #: components/manage/Sidebar/Sidebar +# defaultMessage: Block msgid "Block" msgstr "块" -# defaultMessage: Both email address and password are case sensitive, check that caps lock is not enabled. #: components/theme/Login/Login +# defaultMessage: Both email address and password are case sensitive, check that caps lock is not enabled. msgid "Both email address and password are case sensitive, check that caps lock is not enabled." msgstr "Email 地址和密码都是区分大小写的,请检查 CapsLock 键。" -# defaultMessage: Breadcrumbs #: components/theme/Breadcrumbs/Breadcrumbs +# defaultMessage: Breadcrumbs msgid "Breadcrumbs" msgstr "面包屑导航" -# defaultMessage: Browse #: components/manage/Blocks/HeroImageLeft/Edit #: components/manage/Contents/ContentsUploadModal #: components/manage/Sidebar/ObjectBrowserNav +# defaultMessage: Browse msgid "Browse" msgstr "浏览" -# defaultMessage: Browse the site, drop an image, or type an URL #: components/manage/Blocks/Image/Edit +# defaultMessage: Browse the site, drop an image, or type an URL msgid "Browse the site, drop an image, or type an URL" msgstr "浏览网站,放入一张图片,或者输入一个URL" -# defaultMessage: By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator. #: components/manage/Sharing/Sharing +# defaultMessage: By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator. msgid "By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator." msgstr "默认情况下,条目的权限从父文件夹继承,您可以禁用继承,明确设置权限。总体来说,${inherited}符号表示这是一个继承的值,${global}符号表示这是一个由网站管理员管理的全局角色。" -# defaultMessage: By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first. #: components/manage/Contents/Contents +# defaultMessage: By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first. msgid "By deleting this item, you will break links that exist in the items listed below. If this is indeed what you want to do, we recommend that remove these references first." msgstr "删除这些条目,将会破坏下列条目中的链连引用,如果您确定要这样做,建议您先删除这些引用。" -# defaultMessage: Cache Name #: components/manage/Controlpanels/DatabaseInformation +# defaultMessage: Cache Name msgid "Cache Name" msgstr "缓存名称" -# defaultMessage: Can not edit Layout for {type} content-type as it doesn't have support for Volto Blocks enabled #: components/manage/Controlpanels/ContentTypeLayout +# defaultMessage: Can not edit Layout for {type} content-type as it doesn't have support for Volto Blocks enabled msgid "Can not edit Layout for {type} content-type as it doesn't have support for Volto Blocks enabled" msgstr "" -# defaultMessage: Can not edit Layout for {type} content-type as the Blocks behavior is enabled and read-only #: components/manage/Controlpanels/ContentTypeLayout +# defaultMessage: Can not edit Layout for {type} content-type as the Blocks behavior is enabled and read-only msgid "Can not edit Layout for {type} content-type as the Blocks behavior is enabled and read-only" msgstr "" -# defaultMessage: Cancel -#: components/manage/Add/Add components/manage/Contents/ContentsUploadModal +#: components/manage/Add/Add +#: components/manage/Contents/ContentsUploadModal #: components/manage/Controlpanels/ContentType #: components/manage/Controlpanels/ContentTypeLayout #: components/manage/Controlpanels/ContentTypeSchema #: components/manage/Controlpanels/Controlpanel #: components/manage/Controlpanels/Rules/AddRule #: components/manage/Controlpanels/Rules/EditRule -#: components/manage/Delete/Delete components/manage/Edit/Edit -#: components/manage/Form/ModalForm components/manage/Sharing/Sharing -#: components/theme/Login/Login helpers/MessageLabels/MessageLabels +#: components/manage/Delete/Delete +#: components/manage/Edit/Edit +#: components/manage/Form/ModalForm +#: components/manage/Sharing/Sharing +#: components/theme/Login/Login +#: helpers/MessageLabels/MessageLabels +# defaultMessage: Cancel msgid "Cancel" msgstr "取消" -# defaultMessage: Cell #: components/manage/Blocks/Table/Edit +# defaultMessage: Cell msgid "Cell" msgstr "单元格" -# defaultMessage: Center -#: components/manage/Blocks/Maps/Edit components/manage/Sidebar/AlignBlock +#: components/manage/Blocks/Maps/Edit +#: components/manage/Sidebar/AlignBlock #: components/manage/Widgets/AlignWidget +# defaultMessage: Center msgid "Center" msgstr "中间" -# defaultMessage: Change Note #: components/manage/History/History +# defaultMessage: Change Note msgid "Change Note" msgstr "更改注释" -# defaultMessage: Change Password #: components/manage/Preferences/ChangePassword +# defaultMessage: Change Password msgid "Change Password" msgstr "更改密码" -# defaultMessage: Change State #: components/manage/Contents/ContentsWorkflowModal +# defaultMessage: Change State msgid "Change State" msgstr "改变状态" -# defaultMessage: Change workflow state recursively #: components/manage/Contents/ContentsWorkflowModal +# defaultMessage: Change workflow state recursively msgid "Change workflow state recursively" msgstr "递归地更改工作流状态" -# defaultMessage: Changes applied #: components/manage/Toolbar/More +# defaultMessage: Changes applied msgid "Changes applied." msgstr "更改已经生效" -# defaultMessage: Changes saved #: components/manage/Preferences/ChangePassword #: components/manage/Preferences/PersonalPreferences +# defaultMessage: Changes saved msgid "Changes saved" msgstr "更改已经保存" -# defaultMessage: Changes saved. #: components/manage/Controlpanels/ContentType #: components/manage/Controlpanels/ContentTypeLayout #: components/manage/Controlpanels/ContentTypeSchema #: components/manage/Controlpanels/Controlpanel +# defaultMessage: Changes saved. msgid "Changes saved." msgstr "更改已经保存" -# defaultMessage: Checkbox #: components/manage/Widgets/SchemaWidget +# defaultMessage: Checkbox msgid "Checkbox" msgstr "复选框" -# defaultMessage: Choices #: components/manage/Widgets/SelectWidget +# defaultMessage: Choices msgid "Choices" msgstr "选择" -# defaultMessage: Choose Image #: components/manage/Sidebar/ObjectBrowserBody +# defaultMessage: Choose Image msgid "Choose Image" msgstr "选择图像" -# defaultMessage: Choose Target #: components/manage/Sidebar/ObjectBrowserBody +# defaultMessage: Choose Target msgid "Choose Target" msgstr "选择目标" -# defaultMessage: Choose a file #: components/manage/Widgets/FileWidget +# defaultMessage: Choose a file msgid "Choose a file" msgstr "选择一个文件" -# defaultMessage: Clear #: components/manage/Blocks/HTML/Edit +# defaultMessage: Clear msgid "Clear" msgstr "清除" -# defaultMessage: Clear filters #: components/manage/Blocks/Search/components/FilterList +# defaultMessage: Clear filters msgid "Clear filters" msgstr "清除过滤器" -# defaultMessage: Click to download full sized image #: components/theme/View/ImageView +# defaultMessage: Click to download full sized image msgid "Click to download full sized image" msgstr "点击下载完整大小的图片" -# defaultMessage: Close #: components/manage/Widgets/SelectWidget +# defaultMessage: Close msgid "Close" msgstr "关闭" -# defaultMessage: Close menu #: components/theme/Navigation/Navigation +# defaultMessage: Close menu msgid "Close menu" msgstr "关闭菜单" -# defaultMessage: Code #: components/manage/Blocks/HTML/Edit +# defaultMessage: Code msgid "Code" msgstr "代码" -# defaultMessage: Collapse item #: components/manage/Widgets/ObjectListWidget +# defaultMessage: Collapse item msgid "Collapse item" msgstr "崩溃的项目" -# defaultMessage: Collection #: components/manage/Toolbar/Toolbar +# defaultMessage: Collection msgid "Collection" msgstr "查询集" -# defaultMessage: Color #: components/manage/Widgets/ColorPickerWidget +# defaultMessage: Color msgid "Color" msgstr "颜色" -# defaultMessage: Comment #: components/manage/Controlpanels/ModerateComments #: components/theme/Comments/CommentEditModal #: components/theme/Comments/Comments +# defaultMessage: Comment msgid "Comment" msgstr "评论" -# defaultMessage: Commenter #: components/manage/Controlpanels/ModerateComments +# defaultMessage: Commenter msgid "Commenter" msgstr "评论者" -# defaultMessage: Comments #: components/theme/Comments/Comments +# defaultMessage: Comments msgid "Comments" msgstr "评论" -# defaultMessage: Compare #: components/manage/Diff/Diff +# defaultMessage: Compare msgid "Compare" msgstr "比较" -# defaultMessage: Condition changed #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Condition changed msgid "Condition changed" msgstr "条件改变" -# defaultMessage: Condition: #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Condition: msgid "Condition: " msgstr "条件" -# defaultMessage: Configuration Versions #: components/manage/Controlpanels/UpgradeControlPanel +# defaultMessage: Configuration Versions msgid "Configuration Versions" msgstr "配置版本" -# defaultMessage: Configure Content Rule #: components/manage/Controlpanels/Rules/EditRule +# defaultMessage: Configure Content Rule msgid "Configure Content Rule" msgstr "配置内容规则" -# defaultMessage: Configure Content Rule: {title} #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Configure Content Rule: {title} msgid "Configure Content Rule: {title}" msgstr "配置内容规则: {title}" -# defaultMessage: Configure content rule #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Configure content rule msgid "Configure content rule" msgstr "配置内容规则" -# defaultMessage: Confirm password #: components/manage/Preferences/ChangePassword #: components/theme/PasswordReset/PasswordReset +# defaultMessage: Confirm password msgid "Confirm password" msgstr "确认密码" -# defaultMessage: Connection refused #: components/theme/ConnectionRefused/ConnectionRefused +# defaultMessage: Connection refused msgid "Connection refused" msgstr "连接被拒绝" -# defaultMessage: Contact form #: components/theme/ContactForm/ContactForm +# defaultMessage: Contact form msgid "Contact form" msgstr "联系表单" -# defaultMessage: Contained items #: components/manage/Blocks/Listing/Edit +# defaultMessage: Contained items msgid "Contained items" msgstr "包含的项目" -# defaultMessage: Content #: components/manage/Controlpanels/Controlpanels +# defaultMessage: Content msgid "Content" msgstr "内容" -# defaultMessage: Content Rule #: components/manage/Controlpanels/Rules/Rules +# defaultMessage: Content Rule msgid "Content Rule" msgstr "内容规则" -# defaultMessage: Content Rules #: components/manage/Controlpanels/Controlpanels #: components/manage/Controlpanels/Rules/Rules +# defaultMessage: Content Rules msgid "Content Rules" msgstr "内容规则" -# defaultMessage: Content rules for {title} #: components/manage/Rules/Rules +# defaultMessage: Content rules for {title} msgid "Content rules for {title}" msgstr "{title}的内容规则" -# defaultMessage: Content rules from parent folders #: components/manage/Rules/Rules +# defaultMessage: Content rules from parent folders msgid "Content rules from parent folders" msgstr "父文件夹的内容规则" -# defaultMessage: Content type created #: components/manage/Controlpanels/ContentTypes +# defaultMessage: Content type created msgid "Content type created" msgstr "内容类型已创建" -# defaultMessage: Content type deleted #: components/manage/Controlpanels/ContentTypes +# defaultMessage: Content type deleted msgid "Content type deleted" msgstr "内容类型已删除" +#: components/manage/Contents/Contents +#: components/manage/Toolbar/Toolbar # defaultMessage: Contents -#: components/manage/Contents/Contents components/manage/Toolbar/Toolbar msgid "Contents" msgstr "内容" -# defaultMessage: Controls #: components/manage/Blocks/Search/schema +# defaultMessage: Controls msgid "Controls" msgstr "控制" -# defaultMessage: Copy -#: components/manage/Actions/Actions components/manage/Contents/Contents +#: components/manage/Actions/Actions +#: components/manage/Contents/Contents #: components/manage/Contents/ContentsItem +# defaultMessage: Copy msgid "Copy" msgstr "复制" -# defaultMessage: undefined #: helpers/MessageLabels/MessageLabels +# defaultMessage: undefined msgid "Copy blocks" msgstr "复制块" -# defaultMessage: Copyright #: components/theme/Footer/Footer +# defaultMessage: Copyright msgid "Copyright" msgstr "版权" -# defaultMessage: Copyright statement or other rights information on this item. #: components/manage/Contents/ContentsPropertiesModal +# defaultMessage: Copyright statement or other rights information on this item. msgid "Copyright statement or other rights information on this item." msgstr "本内容的版权信息" -# defaultMessage: Create working copy #: components/manage/Toolbar/More +# defaultMessage: Create working copy msgid "Create working copy" msgstr "创建工作副本" -# defaultMessage: Created by {creator} on {date} #: components/manage/WorkingCopyToastsFactory/WorkingCopyToastsFactory +# defaultMessage: Created by {creator} on {date} msgid "Created by {creator} on {date}" msgstr "由{creator}在{date}时创建" -# defaultMessage: Created on #: components/manage/Contents/Contents +# defaultMessage: Created on msgid "Created on" msgstr "创建于" -# defaultMessage: Creator #: components/manage/Contents/Contents +# defaultMessage: Creator msgid "Creator" msgstr "创建者" -# defaultMessage: Creators #: components/manage/Contents/ContentsPropertiesModal +# defaultMessage: Creators msgid "Creators" msgstr "创建者" -# defaultMessage: Criteria #: components/manage/Widgets/QuerystringWidget #: components/manage/Widgets/QueryWidget +# defaultMessage: Criteria msgid "Criteria" msgstr "标准" -# defaultMessage: Current active configuration #: components/manage/Controlpanels/UpgradeControlPanel +# defaultMessage: Current active configuration msgid "Current active configuration" msgstr "当前活动的配置" -# defaultMessage: Current filters applied #: components/manage/Blocks/Search/components/FilterList +# defaultMessage: Current filters applied msgid "Current filters applied" msgstr "当前应用的过滤器" -# defaultMessage: Current password #: components/manage/Preferences/ChangePassword +# defaultMessage: Current password msgid "Current password" msgstr "当前的密码" -# defaultMessage: Cut -#: components/manage/Actions/Actions components/manage/Contents/Contents +#: components/manage/Actions/Actions +#: components/manage/Contents/Contents #: components/manage/Contents/ContentsItem +# defaultMessage: Cut msgid "Cut" msgstr "剪切" -# defaultMessage: undefined #: helpers/MessageLabels/MessageLabels +# defaultMessage: undefined msgid "Cut blocks" msgstr "剪切块" -# defaultMessage: Daily #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: Daily msgid "Daily" msgstr "每日" -# defaultMessage: Database #: components/manage/Controlpanels/Controlpanels +# defaultMessage: Database msgid "Database" msgstr "数据库" -# defaultMessage: Database Information #: components/manage/Controlpanels/DatabaseInformation +# defaultMessage: Database Information msgid "Database Information" msgstr "数据库信息" -# defaultMessage: Database Location #: components/manage/Controlpanels/DatabaseInformation +# defaultMessage: Database Location msgid "Database Location" msgstr "数据库位置" -# defaultMessage: Database Size #: components/manage/Controlpanels/DatabaseInformation +# defaultMessage: Database Size msgid "Database Size" msgstr "数据库大小" -# defaultMessage: Database main #: components/manage/Controlpanels/DatabaseInformation +# defaultMessage: Database main msgid "Database main" msgstr "" -# defaultMessage: Date #: components/manage/Controlpanels/Aliases #: components/manage/Controlpanels/ModerateComments #: components/manage/Widgets/DatetimeWidget +# defaultMessage: Date msgid "Date" msgstr "日期" -# defaultMessage: Date (newest first) #: components/theme/Search/Search +# defaultMessage: Date (newest first) msgid "Date (newest first)" msgstr "日期(最新在前)" -# defaultMessage: Default #: components/manage/Contents/ContentsPropertiesModal #: components/manage/Contents/ContentsRenameModal #: components/manage/Contents/ContentsTagsModal @@ -870,2229 +889,2273 @@ msgstr "日期(最新在前)" #: components/manage/Widgets/SelectWidget #: components/manage/Widgets/WysiwygWidget #: components/theme/Comments/CommentEditModal -#: components/theme/Comments/Comments components/theme/ContactForm/ContactForm +#: components/theme/Comments/Comments +#: components/theme/ContactForm/ContactForm #: components/theme/PasswordReset/PasswordReset #: components/theme/PasswordReset/RequestPasswordReset #: components/theme/Register/Register +# defaultMessage: Default msgid "Default" msgstr "默认" -# defaultMessage: Default view #: config/Views +# defaultMessage: Default view msgid "Default view" msgstr "默认视图" -# defaultMessage: Delete -#: components/manage/Contents/Contents components/manage/Contents/ContentsItem +#: components/manage/Contents/Contents +#: components/manage/Contents/ContentsItem #: components/manage/Controlpanels/ContentTypesActions #: components/manage/Controlpanels/Groups/RenderGroups #: components/manage/Controlpanels/ModerateComments #: components/manage/Controlpanels/Users/RenderUsers -#: components/manage/Delete/Delete components/manage/Widgets/FormFieldWrapper +#: components/manage/Delete/Delete +#: components/manage/Widgets/FormFieldWrapper #: components/manage/Widgets/ObjectBrowserWidget -#: components/manage/Widgets/WysiwygWidget components/theme/Comments/Comments +#: components/manage/Widgets/WysiwygWidget +#: components/theme/Comments/Comments +# defaultMessage: Delete msgid "Delete" msgstr "删除" -# defaultMessage: Delete Group #: helpers/MessageLabels/MessageLabels +# defaultMessage: Delete Group msgid "Delete Group" msgstr "删除组" -# defaultMessage: Delete Type #: components/manage/Controlpanels/ContentTypes +# defaultMessage: Delete Type msgid "Delete Type" msgstr "删除类型" -# defaultMessage: Delete User #: helpers/MessageLabels/MessageLabels +# defaultMessage: Delete User msgid "Delete User" msgstr "删除用户" -# defaultMessage: Action deleted #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Action deleted msgid "Delete action" msgstr "删除操作" -# defaultMessage: undefined #: helpers/MessageLabels/MessageLabels +# defaultMessage: undefined msgid "Delete blocks" msgstr "删除块" -# defaultMessage: Delete col #: components/manage/Blocks/Table/Edit +# defaultMessage: Delete col msgid "Delete col" msgstr "删除 col" -# defaultMessage: Condition deleted #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Condition deleted msgid "Delete condition" msgstr "删除条件" -# defaultMessage: Delete row #: components/manage/Blocks/Table/Edit +# defaultMessage: Delete row msgid "Delete row" msgstr "删除条件" -# defaultMessage: Deleted #: components/manage/Controlpanels/Rules/Rules +# defaultMessage: Deleted msgid "Deleted" msgstr "已删除" -# defaultMessage: Depth #: components/manage/Widgets/QuerystringWidget +# defaultMessage: Depth msgid "Depth" msgstr "深度" -# defaultMessage: Descending #: components/manage/Blocks/Search/components/SortOn #: components/manage/Contents/Contents +# defaultMessage: Descending msgid "Descending" msgstr "降序" -# defaultMessage: Description #: components/manage/Blocks/HeroImageLeft/Edit #: components/manage/Controlpanels/ContentTypes #: components/manage/Widgets/SchemaWidget #: components/manage/Widgets/SelectWidget -#: components/manage/Widgets/WysiwygWidget components/theme/View/TabularView +#: components/manage/Widgets/WysiwygWidget +#: components/theme/View/TabularView #: helpers/MessageLabels/MessageLabels +# defaultMessage: Description msgid "Description" msgstr "描述" -# defaultMessage: Diff #: components/manage/Diff/Diff +# defaultMessage: Diff msgid "Diff" msgstr "不同" -# defaultMessage: Difference between revision {one} and {two} of {title} #: components/manage/Diff/Diff +# defaultMessage: Difference between revision {one} and {two} of {title} msgid "Difference between revision {one} and {two} of {title}" msgstr "{title}版本 {one} 和 {two} 之间的区别" -# defaultMessage: Disable #: components/manage/Rules/Rules +# defaultMessage: Disable msgid "Disable" msgstr "禁用" -# defaultMessage: Disable apply to subfolders #: components/manage/Rules/Rules +# defaultMessage: Disable apply to subfolders msgid "Disable apply to subfolders" msgstr "禁用于子文件夹" -# defaultMessage: Disabled #: components/manage/Rules/Rules +# defaultMessage: Disabled msgid "Disabled" msgstr "禁用" -# defaultMessage: Disabled apply to subfolders #: components/manage/Rules/Rules +# defaultMessage: Disabled apply to subfolders msgid "Disabled apply to subfolders" msgstr "禁用于子文件夹" -# defaultMessage: Distributed under the {license}. #: components/theme/Footer/Footer +# defaultMessage: Distributed under the {license}. msgid "Distributed under the {license}." msgstr "使用{license} 发布" -# defaultMessage: Add border to inner columns #: components/manage/Blocks/Table/Edit +# defaultMessage: Add border to inner columns msgid "Divide each row into separate cells" msgstr "将每一行分成独立的单元格" -# defaultMessage: Do you really want to delete the following items? #: components/manage/Contents/Contents +# defaultMessage: Do you really want to delete the following items? msgid "Do you really want to delete the following items?" msgstr "确定要删除这个条目吗?" -# defaultMessage: Do you really want to delete the group {groupname}? #: components/manage/Controlpanels/Groups/GroupsControlpanel +# defaultMessage: Do you really want to delete the group {groupname}? msgid "Do you really want to delete the group {groupname}?" msgstr "确定要删除这个组 {groupname}吗?" -# defaultMessage: Do you really want to delete type {typename}? #: components/manage/Controlpanels/ContentTypes +# defaultMessage: Do you really want to delete type {typename}? msgid "Do you really want to delete the type {typename}?" msgstr "确定要删除这个类型 {typename}吗?" -# defaultMessage: Do you really want to delete the user {username}? #: components/manage/Controlpanels/Users/UsersControlpanel +# defaultMessage: Do you really want to delete the user {username}? msgid "Do you really want to delete the user {username}?" msgstr "确定要删除这个用户 {username}吗?" -# defaultMessage: Do you really want to delete this item? #: components/manage/Delete/Delete +# defaultMessage: Do you really want to delete this item? msgid "Do you really want to delete this item?" msgstr "确定要删除这个条目吗" -# defaultMessage: Document #: components/manage/Multilingual/TranslationObject #: components/manage/Sidebar/Sidebar +# defaultMessage: Document msgid "Document" msgstr "文档" -# defaultMessage: Document view #: config/Views +# defaultMessage: Document view msgid "Document view" msgstr "文档视图" -# defaultMessage: Download Event #: components/theme/EventDetails/EventDetails +# defaultMessage: Download Event msgid "Download Event" msgstr "下载事件" -# defaultMessage: Drag and drop files from your computer onto this area or click the “Browse” button. #: components/manage/Contents/ContentsUploadModal +# defaultMessage: Drag and drop files from your computer onto this area or click the “Browse” button. msgid "Drag and drop files from your computer onto this area or click the “Browse” button." msgstr "从你的电脑中拖放文件到此区域或单击 “浏览” 按钮" -# defaultMessage: Drop file here to replace the existing file #: components/manage/Widgets/FileWidget +# defaultMessage: Drop file here to replace the existing file msgid "Drop file here to replace the existing file" msgstr "在此处放置文件以替换现有文件" -# defaultMessage: Drop file here to upload a new file #: components/manage/Widgets/FileWidget +# defaultMessage: Drop file here to upload a new file msgid "Drop file here to upload a new file" msgstr "在此处放置文件来上传新文件" -# defaultMessage: Drop files here ... #: components/manage/Widgets/FileWidget +# defaultMessage: Drop files here ... msgid "Drop files here ..." msgstr "在此处放置文件 ..." -# defaultMessage: Dry run selected, transaction aborted. #: components/manage/Controlpanels/UpgradeControlPanel +# defaultMessage: Dry run selected, transaction aborted. msgid "Dry run selected, transaction aborted." msgstr ",事务已被终止。" -# defaultMessage: E-mail #: components/theme/Register/Register +# defaultMessage: E-mail msgid "E-mail" msgstr "E-mail" -# defaultMessage: E-mail addresses do not match. #: components/theme/PasswordReset/PasswordReset +# defaultMessage: E-mail addresses do not match. msgid "E-mail addresses do not match." msgstr "邮箱地址不匹配" -# defaultMessage: Edit #: components/manage/Contents/ContentsItem #: components/manage/Controlpanels/ContentTypesActions #: components/manage/Controlpanels/ModerateComments #: components/manage/Controlpanels/Rules/EditRule -#: components/manage/Toolbar/Toolbar components/manage/Widgets/FormFieldWrapper +#: components/manage/Toolbar/Toolbar +#: components/manage/Widgets/FormFieldWrapper #: components/manage/Widgets/ObjectBrowserWidget #: components/theme/Comments/Comments +# defaultMessage: Edit msgid "Edit" msgstr "编辑" -# defaultMessage: Edit Rule #: components/manage/Controlpanels/Rules/EditRule +# defaultMessage: Edit Rule msgid "Edit Rule" msgstr "编辑规则" -# defaultMessage: Edit comment #: components/theme/Comments/CommentEditModal +# defaultMessage: Edit comment msgid "Edit comment" msgstr "编辑评论" -# defaultMessage: Edit field #: components/manage/Widgets/SchemaWidget +# defaultMessage: Edit field msgid "Edit field" msgstr "编辑字段" -# defaultMessage: Edit fieldset #: components/manage/Widgets/SchemaWidget +# defaultMessage: Edit fieldset msgid "Edit fieldset" msgstr "编辑字段集" -# defaultMessage: Edit recurrence #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: Edit recurrence msgid "Edit recurrence" msgstr "编辑重现" -# defaultMessage: Edit values #: components/manage/Form/InlineForm +# defaultMessage: Edit values msgid "Edit values" msgstr "编辑值" -# defaultMessage: Edit {title} #: components/manage/Edit/Edit +# defaultMessage: Edit {title} msgid "Edit {title}" msgstr "编辑 {title}" -# defaultMessage: Email #: helpers/MessageLabels/MessageLabels +# defaultMessage: Email msgid "Email" msgstr "Email" -# defaultMessage: Email sent #: components/theme/ContactForm/ContactForm +# defaultMessage: Email sent msgid "Email sent" msgstr "发送邮件" -# defaultMessage: Embed code error, please follow the instructions and try again. #: components/manage/Blocks/Maps/Edit +# defaultMessage: Embed code error, please follow the instructions and try again. msgid "Embed code error, please follow the instructions and try again." msgstr "嵌入代码错误,请按照说明重试" -# defaultMessage: Empty object list #: components/manage/Widgets/ObjectListWidget +# defaultMessage: Empty object list msgid "Empty object list" msgstr "空的对象列表" -# defaultMessage: Enable #: components/manage/Rules/Rules +# defaultMessage: Enable msgid "Enable" msgstr "启用" -# defaultMessage: Enable editable Blocks #: components/manage/Controlpanels/ContentTypeLayout +# defaultMessage: Enable editable Blocks msgid "Enable editable Blocks" msgstr "启用可编辑块" -# defaultMessage: Enabled #: components/manage/Rules/Rules +# defaultMessage: Enabled msgid "Enabled" msgstr "启用" -# defaultMessage: Enabled here? #: components/manage/Rules/Rules +# defaultMessage: Enabled here? msgid "Enabled here?" msgstr "在这里启用?" -# defaultMessage: Enabled? #: components/manage/Rules/Rules +# defaultMessage: Enabled? msgid "Enabled?" msgstr "启用?" -# defaultMessage: End Date #: components/manage/Blocks/Search/components/DateRangeFacet #: components/manage/Contents/Contents +# defaultMessage: End Date msgid "End Date" msgstr "结束日期" -# defaultMessage: Enter URL or select an item #: components/manage/AnchorPlugin/components/LinkButton/AddLinkForm +# defaultMessage: Enter URL or select an item msgid "Enter URL or select an item" msgstr "输入 URL 或选择一个项目" -# defaultMessage: Enter a username above to search or click 'Show All' #: helpers/MessageLabels/MessageLabels +# defaultMessage: Enter a username above to search or click 'Show All' msgid "Enter a username above to search or click 'Show All'" msgstr "输入用户名进行搜索或点击显示全部" -# defaultMessage: Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. #: components/theme/Register/Register +# defaultMessage: Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere. msgid "Enter an email address. This will be your login name. We respect your privacy, and will not give the address away to any third parties or expose it anywhere." msgstr "输入一个 Email 地址。这会成为您的登录名。我们尊重您的隐私,不会向第三方透漏您的个人信息。" -# defaultMessage: Enter full name, e.g. John Smith. #: components/theme/Register/Register +# defaultMessage: Enter full name, e.g. John Smith. msgid "Enter full name, e.g. John Smith." msgstr "输入您的姓名,如:张三" -# defaultMessage: Enter map Embed Code #: components/manage/Blocks/Maps/Edit +# defaultMessage: Enter map Embed Code msgid "Enter map Embed Code" msgstr "输入 map 嵌入代码" -# defaultMessage: Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target. #: components/manage/Controlpanels/Aliases +# defaultMessage: Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target. msgid "Enter the absolute path of the target. The path must start with '/'. Target must exist or be an existing alternative url path to the target." msgstr "输入目标的绝对路径。路径必须以‘/’开头。目标必须存在,或者是指向目标的现有可选的url路径。" +#: components/manage/Aliases/Aliases +#: components/manage/Controlpanels/Aliases # defaultMessage: Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring. -#: components/manage/Aliases/Aliases components/manage/Controlpanels/Aliases msgid "Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring." msgstr "输入已存在替代 URL 的绝对路径。路径必须以 '/' 开头。只有导致404 未找到页面的url才会发生重定向。" -# defaultMessage: Enter your current password. #: components/manage/Preferences/ChangePassword +# defaultMessage: Enter your current password. msgid "Enter your current password." msgstr "输入当前密码" -# defaultMessage: Enter your email for verification. #: components/theme/PasswordReset/PasswordReset +# defaultMessage: Enter your email for verification. msgid "Enter your email for verification." msgstr "输入你的邮箱进行验证" -# defaultMessage: Enter your new password. Minimum 5 characters. #: components/manage/Preferences/ChangePassword #: components/theme/PasswordReset/PasswordReset +# defaultMessage: Enter your new password. Minimum 5 characters. msgid "Enter your new password. Minimum 5 characters." msgstr "输入你的新密码。最少5个字符" -# defaultMessage: Enter your username for verification. #: components/theme/PasswordReset/PasswordReset +# defaultMessage: Enter your username for verification. msgid "Enter your username for verification." msgstr "输入你的用户名进行验证" -# defaultMessage: Error -#: components/manage/Add/Add components/manage/Controlpanels/AddonsControlpanel +#: components/manage/Add/Add +#: components/manage/Controlpanels/AddonsControlpanel #: components/manage/Controlpanels/ContentTypeSchema -#: components/manage/Controlpanels/UndoControlpanel components/manage/Edit/Edit -#: components/manage/Form/InlineForm components/manage/Toolbar/More +#: components/manage/Controlpanels/UndoControlpanel +#: components/manage/Edit/Edit +#: components/manage/Form/InlineForm +#: components/manage/Toolbar/More #: components/manage/Widgets/SchemaWidget -#: components/theme/ContactForm/ContactForm components/theme/Login/Login +#: components/theme/ContactForm/ContactForm +#: components/theme/Login/Login #: helpers/MessageLabels/MessageLabels +# defaultMessage: Error msgid "Error" msgstr "错误" -# defaultMessage: Error #: components/manage/Controlpanels/Aliases +# defaultMessage: Error msgid "ErrorHeader" msgstr "" -# defaultMessage: Event #: components/manage/Controlpanels/Rules/Rules +# defaultMessage: Event msgid "Event" msgstr "事件" -# defaultMessage: Event listing #: config/Views +# defaultMessage: Event listing msgid "Event listing" msgstr "事件列表" -# defaultMessage: Event view #: config/Views +# defaultMessage: Event view msgid "Event view" msgstr "事件视图" -# defaultMessage: Exclude from navigation #: components/manage/Contents/ContentsPropertiesModal +# defaultMessage: Exclude from navigation msgid "Exclude from navigation" msgstr "从导航中排除" -# defaultMessage: Exclude this occurence #: components/manage/Widgets/RecurrenceWidget/Occurences +# defaultMessage: Exclude this occurence msgid "Exclude this occurence" msgstr "排除这个事件" -# defaultMessage: Excluded from navigation #: components/manage/Contents/Contents +# defaultMessage: Excluded from navigation msgid "Excluded from navigation" msgstr "从导航中排除" -# defaultMessage: Existing alternative urls for this item #: components/manage/Aliases/Aliases +# defaultMessage: Existing alternative urls for this item msgid "Existing alternative urls for this item" msgstr "此项目现有可选的urls" -# defaultMessage: Expand sidebar #: components/manage/Sidebar/Sidebar +# defaultMessage: Expand sidebar msgid "Expand sidebar" msgstr "扩大侧边栏" -# defaultMessage: Expiration Date #: components/manage/Contents/ContentsPropertiesModal +# defaultMessage: Expiration Date msgid "Expiration Date" msgstr "失效日期" -# defaultMessage: Expiration date #: components/manage/Contents/Contents +# defaultMessage: Expiration date msgid "Expiration date" msgstr "失效日期" -# defaultMessage: Expired #: components/manage/Contents/ContentsItem +# defaultMessage: Expired msgid "Expired" msgstr "失效" -# defaultMessage: External URL #: components/manage/Blocks/LeadImage/LeadImageSidebar +# defaultMessage: External URL msgid "External URL" msgstr "失效的URL" -# defaultMessage: Facet #: components/manage/Blocks/Search/schema +# defaultMessage: Facet msgid "Facet" msgstr "" -# defaultMessage: Facet widget #: components/manage/Blocks/Search/schema +# defaultMessage: Facet widget msgid "Facet widget" msgstr "" -# defaultMessage: Facets #: components/manage/Blocks/Search/schema +# defaultMessage: Facets msgid "Facets" msgstr "" -# defaultMessage: Facets on left side #: config/Blocks +# defaultMessage: Facets on left side msgid "Facets on left side" msgstr "" -# defaultMessage: Facets on right side #: config/Blocks +# defaultMessage: Facets on right side msgid "Facets on right side" msgstr "" -# defaultMessage: Facets on top #: config/Blocks +# defaultMessage: Facets on top msgid "Facets on top" msgstr "" -# defaultMessage: Failed to undo transactions #: components/manage/Controlpanels/UndoControlpanel +# defaultMessage: Failed to undo transactions msgid "Failed To Undo Transactions" msgstr "撤销事务失败" -# defaultMessage: Field #: components/manage/Blocks/Search/schema +# defaultMessage: Field msgid "Field" msgstr "字段" -# defaultMessage: File #: components/manage/Toolbar/Toolbar +# defaultMessage: File msgid "File" msgstr "文件" -# defaultMessage: File size #: components/manage/Contents/ContentsUploadModal +# defaultMessage: File size msgid "File size" msgstr "文件大小" -# defaultMessage: File view #: config/Views +# defaultMessage: File view msgid "File view" msgstr "文件视图" -# defaultMessage: Filename #: components/manage/Contents/ContentsUploadModal +# defaultMessage: Filename msgid "Filename" msgstr "文件名" -# defaultMessage: Filter Rules: #: components/manage/Controlpanels/Rules/Rules +# defaultMessage: Filter Rules: msgid "Filter Rules:" msgstr "过滤规则" -# defaultMessage: Filter by prefix #: components/manage/Controlpanels/Aliases +# defaultMessage: Filter by prefix msgid "Filter by prefix" msgstr "按前缀过滤" -# defaultMessage: Filter users by groups #: helpers/MessageLabels/MessageLabels +# defaultMessage: Filter users by groups msgid "Filter users by groups" msgstr "按组过滤用户" -# defaultMessage: Filter… #: components/manage/Contents/Contents +# defaultMessage: Filter… msgid "Filter…" msgstr "过滤" -# defaultMessage: First #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField +# defaultMessage: First msgid "First" msgstr "首个" -# defaultMessage: Fixed width columns #: components/manage/Blocks/Table/Edit +# defaultMessage: Fixed width columns msgid "Fixed width table cells" msgstr "固定表格单元格宽度" -# defaultMessage: Fold #: components/manage/BlockChooser/BlockChooser +# defaultMessage: Fold msgid "Fold" msgstr "折叠" -# defaultMessage: Folder #: components/manage/Contents/Contents +# defaultMessage: Folder msgid "Folder" msgstr "文件夹" -# defaultMessage: Folder listing #: config/Views +# defaultMessage: Folder listing msgid "Folder listing" msgstr "文件夹列表" -# defaultMessage: Forbidden #: components/theme/Forbidden/Forbidden +# defaultMessage: Forbidden msgid "Forbidden" msgstr "禁止" -# defaultMessage: Fourth #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField +# defaultMessage: Fourth msgid "Fourth" msgstr "第四" -# defaultMessage: From #: components/theme/ContactForm/ContactForm +# defaultMessage: From msgid "From" msgstr "" -# defaultMessage: Full -#: components/manage/Blocks/Maps/Edit components/manage/Sidebar/AlignBlock +#: components/manage/Blocks/Maps/Edit +#: components/manage/Sidebar/AlignBlock #: components/manage/Widgets/AlignWidget +# defaultMessage: Full msgid "Full" msgstr "" -# defaultMessage: Full Name #: components/theme/Register/Register +# defaultMessage: Full Name msgid "Full Name" msgstr "姓名" -# defaultMessage: Fullname #: helpers/MessageLabels/MessageLabels +# defaultMessage: Fullname msgid "Fullname" msgstr "姓名" -# defaultMessage: GNU GPL license #: components/theme/Footer/Footer +# defaultMessage: GNU GPL license msgid "GNU GPL license" msgstr "GNU GPL 许可" -# defaultMessage: General #: components/manage/Controlpanels/Controlpanels +# defaultMessage: General msgid "General" msgstr "常规" -# defaultMessage: Global role #: components/manage/Sharing/Sharing +# defaultMessage: Global role msgid "Global role" msgstr "全局角色" -# defaultMessage: Google Maps Embedded Block #: components/manage/Blocks/Maps/Edit +# defaultMessage: Google Maps Embedded Block msgid "Google Maps Embedded Block" msgstr "Google 地图嵌入块" -# defaultMessage: Group #: components/manage/Sharing/Sharing +# defaultMessage: Group msgid "Group" msgstr "组" -# defaultMessage: Group created #: helpers/MessageLabels/MessageLabels +# defaultMessage: Group created msgid "Group created" msgstr "组已创建" -# defaultMessage: Group roles updated #: helpers/MessageLabels/MessageLabels +# defaultMessage: Group roles updated msgid "Group roles updated" msgstr "组角色更新" -# defaultMessage: Groupname #: components/manage/Controlpanels/Groups/GroupsControlpanel #: helpers/MessageLabels/MessageLabels +# defaultMessage: Groupname msgid "Groupname" msgstr "组名" -# defaultMessage: Groups #: components/manage/Controlpanels/Controlpanels #: components/manage/Controlpanels/Groups/GroupsControlpanel #: helpers/MessageLabels/MessageLabels +# defaultMessage: Groups msgid "Groups" msgstr "组" -# defaultMessage: Groups are logical collections of users, such as departments and business units. Groups are not directly related to permissions on a global level, you normally use Roles for that - and let certain Groups have a particular role. The symbol{plone_svg}indicates a role inherited from membership in another group. #: components/manage/Controlpanels/Groups/GroupsControlpanel +# defaultMessage: Groups are logical collections of users, such as departments and business units. Groups are not directly related to permissions on a global level, you normally use Roles for that - and let certain Groups have a particular role. The symbol{plone_svg}indicates a role inherited from membership in another group. msgid "Groups are logical collections of users, such as departments and business units. Groups are not directly related to permissions on a global level, you normally use Roles for that - and let certain Groups have a particular role. The symbol{plone_svg}indicates a role inherited from membership in another group." msgstr "组是用户的逻辑集合,如部门和商务小组。他们不直接和某个权限关联,您应该用角色分配权限,然后让组拥有某个角色。符号{plone_svg}表示从另一个组的成员继承的角色" -# defaultMessage: Header cell #: components/manage/Blocks/Table/Edit +# defaultMessage: Header cell msgid "Header cell" msgstr "标题单元格" -# defaultMessage: Headline #: components/manage/Blocks/Listing/schema #: components/manage/Blocks/Search/schema +# defaultMessage: Headline msgid "Headline" msgstr "标题" -# defaultMessage: Headline level #: components/manage/Blocks/Listing/schema +# defaultMessage: Headline level msgid "Headline level" msgstr "标题级别" -# defaultMessage: Hidden facets will still filter the results if proper parameters are passed in URLs #: components/manage/Blocks/Search/schema +# defaultMessage: Hidden facets will still filter the results if proper parameters are passed in URLs msgid "Hidden facets will still filter the results if proper parameters are passed in URLs" msgstr "" -# defaultMessage: Hide Replies #: components/theme/Comments/Comments +# defaultMessage: Hide Replies msgid "Hide Replies" msgstr "隐藏回复" -# defaultMessage: Hide facet? #: components/manage/Blocks/Search/schema +# defaultMessage: Hide facet? msgid "Hide facet?" msgstr "" +#: components/manage/History/History +#: components/manage/Toolbar/More # defaultMessage: History -#: components/manage/History/History components/manage/Toolbar/More msgid "History" msgstr "历史" -# defaultMessage: # #: components/manage/History/History +# defaultMessage: # msgid "History Version Number" msgstr "历史版本号" -# defaultMessage: History of {title} #: components/manage/History/History +# defaultMessage: History of {title} msgid "History of {title}" msgstr "历史{title}" -# defaultMessage: Home #: components/manage/Contents/Contents #: components/manage/Contents/ContentsBreadcrumbs #: components/manage/Contents/ContentsBreadcrumbsHomeItem #: components/theme/Breadcrumbs/Breadcrumbs +# defaultMessage: Home msgid "Home" msgstr "首页" -# defaultMessage: ID #: components/manage/Contents/Contents +# defaultMessage: ID msgid "ID" msgstr "" -# defaultMessage: If all of the following conditions are met: #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: If all of the following conditions are met: msgid "If all of the following conditions are met:" msgstr "如果满足以下所有条件" -# defaultMessage: If selected, this item will not appear in the navigation tree #: components/manage/Contents/ContentsPropertiesModal +# defaultMessage: If selected, this item will not appear in the navigation tree msgid "If selected, this item will not appear in the navigation tree" msgstr "如果选中,此条目不会在导航树中显示" -# defaultMessage: If this date is in the future, the content will not show up in listings and searches until this date. #: components/manage/Contents/ContentsPropertiesModal +# defaultMessage: If this date is in the future, the content will not show up in listings and searches until this date. msgid "If this date is in the future, the content will not show up in listings and searches until this date." msgstr "该条目发布的日期。如果未选择日期,将会立即发布该条目。" -# defaultMessage: If you are certain this user has abandoned the object, you may unlock the object. You will then be able to edit it. #: components/manage/LockingToastsFactory/LockingToastsFactory +# defaultMessage: If you are certain this user has abandoned the object, you may unlock the object. You will then be able to edit it. msgid "If you are certain this user has abandoned the object, you may unlock the object. You will then be able to edit it." msgstr "如果您确定该用户已放弃该对象,则可以解锁该对象。然后您就可以编辑它了。" -# defaultMessage: If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}. #: components/theme/NotFound/NotFound #: components/theme/Unauthorized/Unauthorized +# defaultMessage: If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}. msgid "If you are certain you have the correct web address but are encountering an error, please contact the {site_admin}." msgstr "如果您确定访问的Web地址正确,但是遇到错误,请联系{site_admin}。" -# defaultMessage: Image #: components/manage/Blocks/HeroImageLeft/Edit #: components/manage/Blocks/Image/ImageSidebar #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar +# defaultMessage: Image msgid "Image" msgstr "图像" -# defaultMessage: Image gallery #: config/Blocks +# defaultMessage: Image gallery msgid "Image gallery" msgstr "图像库" -# defaultMessage: Image size #: components/manage/Blocks/Image/schema +# defaultMessage: Image size msgid "Image size" msgstr "图像大小" -# defaultMessage: Image view #: config/Views +# defaultMessage: Image view msgid "Image view" msgstr "图像视图" -# defaultMessage: Include this occurence #: components/manage/Widgets/RecurrenceWidget/Occurences +# defaultMessage: Include this occurence msgid "Include this occurence" msgstr "包括这个事件" -# defaultMessage: Info #: components/manage/Controlpanels/ContentType #: components/manage/Controlpanels/ContentTypeLayout #: components/manage/Controlpanels/ContentTypeSchema #: components/manage/Controlpanels/Controlpanel +# defaultMessage: Info msgid "Info" msgstr "信息" -# defaultMessage: You have selected the option 'many users' or 'many groups'. Thus this control panel asks for input to show users and groups. If you want to see users and groups instantaneous, head over to user group settings. See the button on the left. #: components/manage/Controlpanels/Users/UserGroupMembershipControlPanel +# defaultMessage: You have selected the option 'many users' or 'many groups'. Thus this control panel asks for input to show users and groups. If you want to see users and groups instantaneous, head over to user group settings. See the button on the left. msgid "InfoUserGroupSettings" msgstr "" -# defaultMessage: Inherit permissions from higher levels #: components/manage/Sharing/Sharing +# defaultMessage: Inherit permissions from higher levels msgid "Inherit permissions from higher levels" msgstr "从上层继承权限" -# defaultMessage: Inherited value #: components/manage/Sharing/Sharing +# defaultMessage: Inherited value msgid "Inherited value" msgstr "继承的值" -# defaultMessage: Insert col after #: components/manage/Blocks/Table/Edit +# defaultMessage: Insert col after msgid "Insert col after" msgstr "在后面插入col" -# defaultMessage: Insert col before #: components/manage/Blocks/Table/Edit +# defaultMessage: Insert col before msgid "Insert col before" msgstr "在前面插入col" -# defaultMessage: Insert row after #: components/manage/Blocks/Table/Edit +# defaultMessage: Insert row after msgid "Insert row after" msgstr "在后面插入行" -# defaultMessage: Insert row before #: components/manage/Blocks/Table/Edit +# defaultMessage: Insert row before msgid "Insert row before" msgstr "在前面插入行" -# defaultMessage: Install #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Install msgid "Install" msgstr "安装" -# defaultMessage: Installed #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Installed msgid "Installed" msgstr "已安装" -# defaultMessage: Installed version #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Installed version msgid "Installed version" msgstr "已安装版本" -# defaultMessage: Installing a third party add-on #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Installing a third party add-on msgid "Installing a third party add-on" msgstr "正在安装第三方附加组件" -# defaultMessage: days #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: days msgid "Interval Daily" msgstr "" -# defaultMessage: Month(s) #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: Month(s) msgid "Interval Monthly" msgstr "" -# defaultMessage: week(s) #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: week(s) msgid "Interval Weekly" msgstr "" -# defaultMessage: year(s) #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: year(s) msgid "Interval Yearly" msgstr "" -# defaultMessage: Invalid block - Will be removed on saving +#: components/manage/Blocks/Block/DefaultView #: components/theme/View/RenderBlocks +# defaultMessage: Invalid block - Will be removed on saving msgid "Invalid Block" msgstr "无效的块" -# defaultMessage: Item batch size #: components/manage/Widgets/QuerystringWidget +# defaultMessage: Item batch size msgid "Item batch size" msgstr "条目批量大小" -# defaultMessage: Item succesfully moved. #: components/manage/Contents/Contents +# defaultMessage: Item succesfully moved. msgid "Item succesfully moved." msgstr "成功移除条目。" -# defaultMessage: Item(s) copied. #: components/manage/Contents/Contents +# defaultMessage: Item(s) copied. msgid "Item(s) copied." msgstr "条目已复制" -# defaultMessage: Item(s) cut. #: components/manage/Contents/Contents +# defaultMessage: Item(s) cut. msgid "Item(s) cut." msgstr "条目已剪切" -# defaultMessage: Item(s) has been updated. #: components/manage/Contents/Contents +# defaultMessage: Item(s) has been updated. msgid "Item(s) has been updated." msgstr "条目已更新" +#: components/manage/Actions/Actions +#: components/manage/Contents/Contents # defaultMessage: Item(s) pasted. -#: components/manage/Actions/Actions components/manage/Contents/Contents msgid "Item(s) pasted." msgstr "条目已粘贴" -# defaultMessage: Item(s) state has been updated. #: components/manage/Contents/Contents +# defaultMessage: Item(s) state has been updated. msgid "Item(s) state has been updated." msgstr "条目状态已更新" -# defaultMessage: Items #: components/manage/Controlpanels/ContentTypes +# defaultMessage: Items msgid "Items" msgstr "条目" +#: components/manage/Form/ModalForm +#: helpers/MessageLabels/MessageLabels # defaultMessage: Items must be unique. -#: components/manage/Form/ModalForm helpers/MessageLabels/MessageLabels msgid "Items must be unique." msgstr "条目必须是独特的" -# defaultMessage: Items to be deleted: #: components/manage/Contents/Contents +# defaultMessage: Items to be deleted: msgid "Items to be deleted:" msgstr "需要删除的条目" -# defaultMessage: Label #: components/manage/Blocks/Search/schema +# defaultMessage: Label msgid "Label" msgstr "标签" -# defaultMessage: Language #: components/manage/Preferences/PersonalPreferences +# defaultMessage: Language msgid "Language" msgstr "语言" -# defaultMessage: Language independent field. #: components/manage/Widgets/FormFieldWrapper +# defaultMessage: Language independent field. msgid "Language independent field." msgstr "语言独立字段" -# defaultMessage: Large #: components/manage/Widgets/ImageSizeWidget +# defaultMessage: Large msgid "Large" msgstr "大" -# defaultMessage: Last #: components/manage/Widgets/RecurrenceWidget/WeekdayOfTheMonthIndexField +# defaultMessage: Last msgid "Last" msgstr "最后" -# defaultMessage: Last comment date #: components/manage/Contents/Contents +# defaultMessage: Last comment date msgid "Last comment date" msgstr "最后备注日期" -# defaultMessage: Last modified #: components/manage/Contents/ContentsUploadModal +# defaultMessage: Last modified msgid "Last modified" msgstr "最后修改日期" -# defaultMessage: Latest available configuration #: components/manage/Controlpanels/UpgradeControlPanel +# defaultMessage: Latest available configuration msgid "Latest available configuration" msgstr "最新可用配置" -# defaultMessage: Latest version #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: Latest version msgid "Latest version" msgstr "最新版本" -# defaultMessage: Layout #: components/manage/Controlpanels/ContentTypesActions +# defaultMessage: Layout msgid "Layout" msgstr "布局" -# defaultMessage: Lead Image #: components/manage/Blocks/LeadImage/LeadImageSidebar +# defaultMessage: Lead Image msgid "Lead Image" msgstr "首图" -# defaultMessage: Left -#: components/manage/Blocks/Maps/Edit components/manage/Sidebar/AlignBlock +#: components/manage/Blocks/Maps/Edit +#: components/manage/Sidebar/AlignBlock #: components/manage/Widgets/AlignWidget +# defaultMessage: Left msgid "Left" msgstr "" -# defaultMessage: Link #: components/manage/Toolbar/Toolbar +# defaultMessage: Link msgid "Link" msgstr "链接" -# defaultMessage: Link more #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema +# defaultMessage: Link more msgid "Link more" msgstr "链接更多" -# defaultMessage: Link redirect view #: config/Views +# defaultMessage: Link redirect view msgid "Link redirect view" msgstr "链接重定向视图" -# defaultMessage: Link Title #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Listing/schema +# defaultMessage: Link Title msgid "Link title" msgstr "链接标题" -# defaultMessage: Link to #: components/manage/Blocks/HeroImageLeft/schema #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar #: components/manage/Blocks/Listing/schema +# defaultMessage: Link to msgid "Link to" msgstr "链接至" -# defaultMessage: Link translation for #: components/manage/Multilingual/ManageTranslations +# defaultMessage: Link translation for msgid "Link translation for" msgstr "链接翻译" -# defaultMessage: Listing #: components/manage/Blocks/Listing/schema +# defaultMessage: Listing msgid "Listing" msgstr "列表" -# defaultMessage: Listing view #: config/Views +# defaultMessage: Listing view msgid "Listing view" msgstr "列表视图" -# defaultMessage: Load more... #: components/theme/Comments/Comments +# defaultMessage: Load more... msgid "Load more" msgstr "加载更多" -# defaultMessage: Loading. #: components/manage/Form/ModalForm +# defaultMessage: Loading. msgid "Loading" msgstr "加载中" -# defaultMessage: Login #: components/theme/Login/Login +# defaultMessage: Login msgid "Log In" msgstr "登录" +#: components/theme/Anontools/Anontools +#: components/theme/Login/Login # defaultMessage: Log in -#: components/theme/Anontools/Anontools components/theme/Login/Login msgid "Log in" msgstr "登录" -# defaultMessage: Logged out #: components/theme/Logout/Logout +# defaultMessage: Logged out msgid "Logged out" msgstr "退出" -# defaultMessage: Login #: components/theme/Login/Login +# defaultMessage: Login msgid "Login" msgstr "登录" -# defaultMessage: Login Failed #: components/theme/Login/Login +# defaultMessage: Login Failed msgid "Login Failed" msgstr "登陆失败" -# defaultMessage: Login Name #: components/theme/Login/Login +# defaultMessage: Login Name msgid "Login Name" msgstr "登录名" -# defaultMessage: Logout #: components/manage/Toolbar/PersonalTools +# defaultMessage: Logout msgid "Logout" msgstr "退出" -# defaultMessage: Made by {creator} on {date}. This is not a working copy anymore, but the main content. #: components/manage/Toolbar/More +# defaultMessage: Made by {creator} on {date}. This is not a working copy anymore, but the main content. msgid "Made by {creator} on {date}. This is not a working copy anymore, but the main content." msgstr "由{creator}在{date}时创建。这不再是一个工作副本,而是一个主要内容。" -# defaultMessage: Reduce cell padding #: components/manage/Blocks/Table/Edit +# defaultMessage: Reduce cell padding msgid "Make the table compact" msgstr "使表格紧凑" -# defaultMessage: Manage Translations #: components/manage/Multilingual/ManageTranslations #: components/manage/Toolbar/More +# defaultMessage: Manage Translations msgid "Manage Translations" msgstr "管理翻译" -# defaultMessage: Manage content… #: components/manage/Toolbar/More +# defaultMessage: Manage content… msgid "Manage content…" msgstr "管理内容..." -# defaultMessage: Manage translations for {title} #: components/manage/Multilingual/ManageTranslations +# defaultMessage: Manage translations for {title} msgid "Manage translations for {title}" msgstr "管理{title}的翻译" -# defaultMessage: Manual #: components/manage/Controlpanels/Aliases +# defaultMessage: Manual msgid "Manual" msgstr "" -# defaultMessage: Manually or automatically added? #: components/manage/Controlpanels/Aliases +# defaultMessage: Manually or automatically added? msgid "Manually or automatically added?" msgstr "手动添加还是自动添加?" -# defaultMessage: Maps #: components/manage/Blocks/Maps/MapsSidebar #: components/manage/Blocks/Maps/schema +# defaultMessage: Maps msgid "Maps" msgstr "映射" -# defaultMessage: Maps URL #: components/manage/Blocks/Maps/schema +# defaultMessage: Maps URL msgid "Maps URL" msgstr "映射URL" -# defaultMessage: Maximum length is {len}. #: helpers/MessageLabels/MessageLabels +# defaultMessage: Maximum length is {len}. msgid "Maximum length is {len}." msgstr "最大长度为{len}。" -# defaultMessage: Maximum value is {len}. #: helpers/MessageLabels/MessageLabels +# defaultMessage: Maximum value is {len}. msgid "Maximum value is {len}." msgstr "最大值为{len}。" -# defaultMessage: Medium #: components/manage/Widgets/ImageSizeWidget +# defaultMessage: Medium msgid "Medium" msgstr "中等" -# defaultMessage: Membership updated #: helpers/MessageLabels/MessageLabels +# defaultMessage: Membership updated msgid "Membership updated" msgstr "成员身份更新" -# defaultMessage: Message #: components/theme/ContactForm/ContactForm +# defaultMessage: Message msgid "Message" msgstr "消息" +#: components/manage/Form/ModalForm +#: helpers/MessageLabels/MessageLabels # defaultMessage: Minimum length is {len}. -#: components/manage/Form/ModalForm helpers/MessageLabels/MessageLabels msgid "Minimum length is {len}." msgstr "最短长度为{len}。" -# defaultMessage: Minimum value is {len}. #: helpers/MessageLabels/MessageLabels +# defaultMessage: Minimum value is {len}. msgid "Minimum value is {len}." msgstr "最小值是{len}。" -# defaultMessage: Moderate Comments #: components/manage/Controlpanels/Controlpanels +# defaultMessage: Moderate Comments msgid "Moderate Comments" msgstr "审核评论" -# defaultMessage: Moderate comments #: components/manage/Controlpanels/ModerateComments +# defaultMessage: Moderate comments msgid "Moderate comments" msgstr "审核评论" -# defaultMessage: Monday and Friday #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: Monday and Friday msgid "Monday and Friday" msgstr "周一和周五" -# defaultMessage: Day #: components/manage/Widgets/RecurrenceWidget/ByMonthDayField +# defaultMessage: Day msgid "Month day" msgstr "月日" -# defaultMessage: Monthly #: components/manage/Widgets/RecurrenceWidget/RecurrenceWidget +# defaultMessage: Monthly msgid "Monthly" msgstr "每月" -# defaultMessage: More #: components/manage/Toolbar/Toolbar +# defaultMessage: More msgid "More" msgstr "更多" -# defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. #: components/manage/Controlpanels/UpgradeControlPanel +# defaultMessage: More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide. msgid "More information about the upgrade procedure can be found in the documentation section of plone.org in the Upgrade Guide." msgstr "更多关于升级步骤的信息可以在升级指南中的plone.org文档部分找到。" -# defaultMessage: Mosaic layout #: config/Views +# defaultMessage: Mosaic layout msgid "Mosaic layout" msgstr "" -# defaultMessage: Move down #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Move down msgid "Move down" msgstr "下移" -# defaultMessage: Move to bottom of folder #: components/manage/Contents/ContentsItem +# defaultMessage: Move to bottom of folder msgid "Move to bottom of folder" msgstr "移动至文件夹底部" -# defaultMessage: Move to top of folder #: components/manage/Contents/ContentsItem +# defaultMessage: Move to top of folder msgid "Move to top of folder" msgstr "移动至文件夹顶部" -# defaultMessage: Move up #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Move up msgid "Move up" msgstr "上移" -# defaultMessage: Multiple choices? #: components/manage/Blocks/Search/schema +# defaultMessage: Multiple choices? msgid "Multiple choices?" msgstr "多选?" -# defaultMessage: My email is #: components/theme/PasswordReset/PasswordReset +# defaultMessage: My email is msgid "My email is" msgstr "我的邮箱是" -# defaultMessage: My username is #: components/theme/PasswordReset/PasswordReset +# defaultMessage: My username is msgid "My username is" msgstr "我的用户名是" +#: components/manage/Sharing/Sharing +#: components/theme/ContactForm/ContactForm # defaultMessage: Name -#: components/manage/Sharing/Sharing components/theme/ContactForm/ContactForm msgid "Name" msgstr "名字" -# defaultMessage: Narrow #: components/manage/Widgets/AlignWidget +# defaultMessage: Narrow msgid "Narrow" msgstr "" -# defaultMessage: Navigate back #: error +# defaultMessage: Navigate back msgid "Navigate back" msgstr "导航返回" -# defaultMessage: Navigation #: components/theme/Navigation/ContextNavigation +# defaultMessage: Navigation msgid "Navigation" msgstr "导航" -# defaultMessage: New password #: components/manage/Preferences/ChangePassword #: components/theme/PasswordReset/PasswordReset +# defaultMessage: New password msgid "New password" msgstr "新的密码" -# defaultMessage: News Item #: components/manage/Toolbar/Toolbar +# defaultMessage: News Item msgid "News Item" msgstr "新闻" -# defaultMessage: News item view #: config/Views +# defaultMessage: News item view msgid "News item view" msgstr "新闻视图" -# defaultMessage: No #: components/manage/Contents/ContentsItem #: components/manage/Contents/ContentsPropertiesModal #: components/manage/Controlpanels/ContentTypes +# defaultMessage: No msgid "No" msgstr "否" -# defaultMessage: No transactions found #: components/manage/Controlpanels/UndoControlpanel +# defaultMessage: No transactions found msgid "No Transactions Found" msgstr "" -# defaultMessage: No transactions selected #: components/manage/Controlpanels/UndoControlpanel +# defaultMessage: No transactions selected msgid "No Transactions Selected" msgstr "" -# defaultMessage: No transactions selected to do undo #: components/manage/Controlpanels/UndoControlpanel +# defaultMessage: No transactions selected to do undo msgid "No Transactions Selected To Do Undo" msgstr "" -# defaultMessage: No Video selected #: components/manage/Blocks/Video/VideoSidebar +# defaultMessage: No Video selected msgid "No Video selected" msgstr "未选择视频" -# defaultMessage: No addons found #: components/manage/Controlpanels/VersionOverview +# defaultMessage: No addons found msgid "No addons found" msgstr "没有找到附件" -# defaultMessage: There is no connection to the server, due to a timeout o no network connection. #: components/theme/RequestTimeout/RequestTimeout +# defaultMessage: There is no connection to the server, due to a timeout o no network connection. msgid "No connection to the server" msgstr "与服务器无连接" -# defaultMessage: No image selected #: components/manage/Blocks/Image/ImageSidebar +# defaultMessage: No image selected msgid "No image selected" msgstr "未选择图像" -# defaultMessage: No image set in Lead Image content field #: components/manage/Blocks/LeadImage/LeadImageSidebar +# defaultMessage: No image set in Lead Image content field msgid "No image set in Lead Image content field" msgstr "" -# defaultMessage: No image set in image content field #: components/manage/Blocks/LeadImage/LeadImageSidebar +# defaultMessage: No image set in image content field msgid "No image set in image content field" msgstr "在图像内容字段中没有设置图像" -# defaultMessage: No items found in this container. #: components/manage/Blocks/Listing/ListingBody +# defaultMessage: No items found in this container. msgid "No items found in this container." msgstr "在此容器中没有发现条目。" -# defaultMessage: No items selected #: components/manage/Widgets/ObjectBrowserWidget +# defaultMessage: No items selected msgid "No items selected" msgstr "未选择条目" -# defaultMessage: No map selected #: components/manage/Blocks/Maps/MapsSidebar +# defaultMessage: No map selected msgid "No map selected" msgstr "未选择镜像" -# defaultMessage: No occurences set #: components/manage/Widgets/RecurrenceWidget/Occurences +# defaultMessage: No occurences set msgid "No occurences set" msgstr "" -# defaultMessage: No options #: components/manage/Widgets/ArrayWidget #: components/manage/Widgets/SelectAutoComplete -#: components/manage/Widgets/SelectWidget components/manage/Widgets/TokenWidget +#: components/manage/Widgets/SelectWidget +#: components/manage/Widgets/TokenWidget +# defaultMessage: No options msgid "No options" msgstr "没有选项" +#: components/manage/BlockChooser/BlockChooser +#: components/theme/Search/Search # defaultMessage: No results found -#: components/manage/BlockChooser/BlockChooser components/theme/Search/Search msgid "No results found" msgstr "未找到结果" -# defaultMessage: No results found. #: components/manage/Blocks/Listing/ListingBody #: components/manage/Widgets/ReferenceWidget +# defaultMessage: No results found. msgid "No results found." msgstr "未找到结果。" -# defaultMessage: No selection #: components/manage/Blocks/Search/components/SortOn #: components/manage/Widgets/QuerySortOnWidget #: components/manage/Widgets/QuerystringWidget +# defaultMessage: No selection msgid "No selection" msgstr "没有选择" -# defaultMessage: This addon does not provide an uninstall profile. #: components/manage/Controlpanels/AddonsControlpanel +# defaultMessage: This addon does not provide an uninstall profile. msgid "No uninstall profile" msgstr "没有卸载配置文件" -# defaultMessage: No user found #: helpers/MessageLabels/MessageLabels +# defaultMessage: No user found msgid "No user found" msgstr "未找到用户" -# defaultMessage: No value #: components/manage/Widgets/ArrayWidget #: components/manage/Widgets/ReferenceWidget -#: components/manage/Widgets/SelectUtils components/manage/Widgets/SelectWidget +#: components/manage/Widgets/SelectUtils +#: components/manage/Widgets/SelectWidget +# defaultMessage: No value msgid "No value" msgstr "没有值" -# defaultMessage: No workflow #: components/manage/Workflow/Workflow +# defaultMessage: No workflow msgid "No workflow" msgstr "没有工作流" +#: components/manage/Contents/Contents +#: components/manage/Contents/ContentsItem # defaultMessage: None -#: components/manage/Contents/Contents components/manage/Contents/ContentsItem msgid "None" msgstr "" -# defaultMessage: Note #: components/manage/Controlpanels/UndoControlpanel +# defaultMessage: Note msgid "Note" msgstr "" -# defaultMessage: Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group. #: components/manage/Controlpanels/Users/UsersControlpanel +# defaultMessage: Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group. msgid "Note that roles set here apply directly to a user. The symbol{plone_svg}indicates a role inherited from membership in a group." msgstr "请注意这里设置的角色将直接应用于用户。符号{plone_svg}表示从组成员继承的角色。" -# defaultMessage: Number of active objects #: components/manage/Controlpanels/DatabaseInformation +# defaultMessage: Number of active objects msgid "Number of active objects" msgstr "活动对象的数量" -# defaultMessage: Object Size #: components/manage/Contents/Contents +# defaultMessage: Object Size msgid "Object Size" msgstr "对象大小" -# defaultMessage: occurrence(s) #: components/manage/Widgets/RecurrenceWidget/EndField +# defaultMessage: occurrence(s) msgid "Occurences" msgstr "" -# defaultMessage: Ok #: components/manage/Delete/Delete +# defaultMessage: Ok msgid "Ok" msgstr "" -# defaultMessage: Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed. #: components/manage/Widgets/IdWidget -msgid "Only lowercase letters (a-z) without accents, numbers (0-9), and the characters \"-\", \"_\", and \".\" are allowed." +# defaultMessage: Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed. +msgid "Only lowercase letters (a-z) without accents, numbers (0-9), and the characters "-", "_", and "." are allowed." msgstr "只允许不带读音号的小写字母(a-z),数字(0-9),和符号:_,-,.。" -# defaultMessage: Open in a new tab #: components/manage/Blocks/Image/schema #: components/manage/Blocks/LeadImage/LeadImageSidebar +# defaultMessage: Open in a new tab msgid "Open in a new tab" msgstr "在新标签中打开" -# defaultMessage: Open menu #: components/theme/Navigation/Navigation +# defaultMessage: Open menu msgid "Open menu" msgstr "打开菜单" -# defaultMessage: Open object browser #: components/manage/Widgets/ObjectBrowserWidget +# defaultMessage: Open object browser msgid "Open object browser" msgstr "打开目标浏览器" -# defaultMessage: Origin #: components/manage/Blocks/LeadImage/LeadImageSidebar +# defaultMessage: Origin msgid "Origin" msgstr "原始" -# defaultMessage: Page #: components/manage/Toolbar/Toolbar +# defaultMessage: Page msgid "Page" msgstr "页面" -# defaultMessage: Parent fieldset #: components/manage/Widgets/SchemaWidget +# defaultMessage: Parent fieldset msgid "Parent fieldset" msgstr "父字段集" +#: components/theme/Login/Login +#: helpers/MessageLabels/MessageLabels # defaultMessage: Password -#: components/theme/Login/Login helpers/MessageLabels/MessageLabels msgid "Password" msgstr "密码" -# defaultMessage: Password reset #: components/theme/PasswordReset/PasswordReset #: components/theme/PasswordReset/RequestPasswordReset +# defaultMessage: Password reset msgid "Password reset" msgstr "重置密码" -# defaultMessage: Passwords do not match. #: components/theme/PasswordReset/PasswordReset +# defaultMessage: Passwords do not match. msgid "Passwords do not match." msgstr "密码不匹配。" +#: components/manage/Actions/Actions +#: components/manage/Contents/Contents # defaultMessage: Paste -#: components/manage/Actions/Actions components/manage/Contents/Contents msgid "Paste" msgstr "粘贴" -# defaultMessage: undefined #: helpers/MessageLabels/MessageLabels +# defaultMessage: undefined msgid "Paste blocks" msgstr "粘贴块" -# defaultMessage: Perform the following actions: #: components/manage/Controlpanels/Rules/ConfigureRule +# defaultMessage: Perform the following actions: msgid "Perform the following actions:" msgstr "执行下面的操作:" -# defaultMessage: Permissions have been updated successfully #: components/manage/Sharing/Sharing +# defaultMessage: Permissions have been updated successfully msgid "Permissions have been updated successfully" msgstr "权限更新成功" -# defaultMessage: Permissions updated #: components/manage/Sharing/Sharing +# defaultMessage: Permissions updated msgid "Permissions updated" msgstr "权限更新" -# defaultMessage: Personal Information #: components/manage/Toolbar/Toolbar +# defaultMessage: Personal Information msgid "Personal Information" msgstr "个人信息" -# defaultMessage: Personal Preferences #: components/manage/Preferences/PersonalPreferences #: components/manage/Toolbar/Toolbar +# defaultMessage: Personal Preferences msgid "Personal Preferences" msgstr "个人偏好" +#: components/manage/Toolbar/More +#: components/manage/Toolbar/Toolbar # defaultMessage: Personal tools -#: components/manage/Toolbar/More components/manage/Toolbar/Toolbar msgid "Personal tools" msgstr "个人工具" -# defaultMessage: Persons responsible for creating the content of this item. Please enter a list of user names, one per line. The principal creator should come first. #: components/manage/Contents/ContentsPropertiesModal +# defaultMessage: Persons responsible for creating the content of this item. Please enter a list of user names, one per line. The principal creator should come first. msgid "Persons responsible for creating the content of this item. Please enter a list of user names, one per line. The principal creator should come first." msgstr "负责创建此条目内容的人员。请输入用户名列表,每行一个。主要创建者应放在首位。" -# defaultMessage: Please continue with the upgrade. #: components/manage/Controlpanels/Controlpanels +# defaultMessage: Please continue with the upgrade. msgid "Please continue with the upgrade." msgstr "请继续升级。" -# defaultMessage: Please ensure you have a backup of your site before performing the upgrade. #: components/manage/Controlpanels/UpgradeControlPanel +# defaultMessage: Please ensure you have a backup of your site before performing the upgrade. msgid "Please ensure you have a backup of your site before performing the upgrade." msgstr "在执行升级前,请确定您已对网站进行备份。" -# defaultMessage: Please enter a valid URL by deleting the block and adding a new video block. #: components/manage/Blocks/Video/Body +# defaultMessage: Please enter a valid URL by deleting the block and adding a new video block. msgid "Please enter a valid URL by deleting the block and adding a new video block." msgstr "" -# defaultMessage: Please enter the Embed Code provided by Google Maps -> Share -> Embed map. It should contain the ', @@ -62,10 +59,7 @@ describe('Blocks copy/paste', () => { // GIVEN: A page with multiple blocks cy.intercept('PATCH', '/**/my-page').as('save'); cy.intercept('GET', '/**/my-page').as('content'); - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Maps').click(); + cy.addNewBlock('maps'); cy.get(`.block.maps .toolbar-inner .ui.input input`) .type( '', @@ -105,10 +99,7 @@ describe('Blocks copy/paste', () => { cy.intercept('PATCH', '/**/my-page').as('save'); cy.intercept('GET', '/**/my-page').as('content'); // GIVEN: A page with multiple blocks - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Maps').click(); + cy.addNewBlock('maps'); cy.get(`.block.maps .toolbar-inner .ui.input input`) .type( '', diff --git a/cypress/tests/core/blocks/blocks-listing.js b/cypress/tests/core/blocks/blocks-listing.js index c6de188ce0..bea65dac47 100644 --- a/cypress/tests/core/blocks/blocks-listing.js +++ b/cypress/tests/core/blocks/blocks-listing.js @@ -55,10 +55,7 @@ describe('Listing Block Tests', () => { cy.clearSlateTitle().type('My title'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //verify before save cy.get(`.block.listing .listing-body:first-of-type`).contains( @@ -115,10 +112,7 @@ describe('Listing Block Tests', () => { cy.clearSlateTitle().type('My title'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //verify before save cy.get(`.block.listing .listing-body:first-of-type`).contains( @@ -137,7 +131,7 @@ describe('Listing Block Tests', () => { ); }); - it.only('Add Listing block - results preview', () => { + it('Add Listing block - results preview', () => { cy.intercept('PATCH', '/**/my-page').as('save'); cy.intercept('GET', '/**/my-page').as('content'); cy.intercept('GET', '/**/@types/Document').as('schema'); @@ -171,10 +165,7 @@ describe('Listing Block Tests', () => { cy.clearSlateTitle().type('My title'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); cy.get('.sidebar-container .tabs-wrapper .menu .item') .contains('Block') @@ -250,10 +241,7 @@ describe('Listing Block Tests', () => { cy.clearSlateTitle().type('My title'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //verify before save cy.get(`.block.listing .listing-body:first-of-type`).contains( @@ -308,10 +296,7 @@ describe('Listing Block Tests', () => { cy.clearSlateTitle().type('My title'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //******** add Type criteria filter cy.get('.querystring-widget .fields').contains('Add criteria').click(); @@ -372,10 +357,7 @@ describe('Listing Block Tests', () => { //add listing block cy.scrollTo('bottom'); - cy.getSlate(true).click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing', true); //******** add Type criteria filter cy.get('.sidebar-container .tabs-wrapper .menu .item') @@ -451,10 +433,7 @@ describe('Listing Block Tests', () => { cy.clearSlateTitle().type('Listing block - Test Criteria: short-name'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //******** add short-name criteria filter cy.get('.sidebar-container .tabs-wrapper .menu .item') @@ -544,10 +523,7 @@ describe('Listing Block Tests', () => { ); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //******** add location criteria filter cy.get('.sidebar-container .tabs-wrapper .menu .item') @@ -636,10 +612,7 @@ describe('Listing Block Tests', () => { ); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //******** add location criteria filter cy.get('.sidebar-container .tabs-wrapper .menu .item') @@ -730,10 +703,7 @@ describe('Listing Block Tests', () => { cy.wait('@schema'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //******** add location criteria filter cy.get('.sidebar-container .tabs-wrapper .menu .item') @@ -816,10 +786,7 @@ describe('Listing Block Tests', () => { cy.clearSlateTitle().type('Listing block - respect batching and limits'); //add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Listing').click(); + cy.addNewBlock('listing'); //verify before save cy.get(`.block.listing .listing-body:first-of-type`).contains('My Folder'); diff --git a/cypress/tests/core/blocks/blocks-map.js b/cypress/tests/core/blocks/blocks-map.js index c4872e9671..9644513af9 100644 --- a/cypress/tests/core/blocks/blocks-map.js +++ b/cypress/tests/core/blocks/blocks-map.js @@ -18,10 +18,8 @@ describe('Map Block Tests', () => { it('Add maps block - Google Maps', () => { // when I add a maps block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Maps').click(); + cy.addNewBlock('maps'); + cy.get(`.block.maps .toolbar-inner .ui.input input`) .type( '', @@ -43,10 +41,8 @@ describe('Map Block Tests', () => { it('Add maps block - OpenStreet Maps', () => { // when I add a maps block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Maps').click(); + cy.addNewBlock('maps'); + cy.get(`.block.maps .toolbar-inner .ui.input input`) .type( '
    View Larger Map', diff --git a/cypress/tests/core/blocks/blocks-search.js b/cypress/tests/core/blocks/blocks-search.js index 9fe55fe871..d4ca23f8a9 100644 --- a/cypress/tests/core/blocks/blocks-search.js +++ b/cypress/tests/core/blocks/blocks-search.js @@ -43,10 +43,7 @@ describe('Search Block Tests', () => { cy.getSlateTitle().focus().click().type('My Search Page'); // Add Search listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('Search').click(); + cy.addNewBlock('search'); // Add search query criteria cy.get('#blockform-fieldset-searchquery').click(); diff --git a/cypress/tests/core/blocks/blocks.js b/cypress/tests/core/blocks/blocks.js index 5175c29d80..e206dbc965 100644 --- a/cypress/tests/core/blocks/blocks.js +++ b/cypress/tests/core/blocks/blocks.js @@ -67,10 +67,7 @@ describe('Blocks Tests', () => { it('Add HTML block', () => { // when I add a maps block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common').contains('HTML').click(); + cy.addNewBlock('html'); cy.get(`.block.html .npm__react-simple-code-editor__textarea`).type( `
    This is HTML
    `, ); @@ -92,10 +89,7 @@ describe('Blocks Tests', () => { cy.intercept('PATCH', '*').as('save'); cy.intercept('GET', '/**/my-page').as('content'); // Edit - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.ui.buttons .button.slateTable').click(); + cy.addNewBlock('table'); cy.wait(2000); cy.get( '.celled.fixed.table thead tr th:first-child() [contenteditable="true"]', diff --git a/cypress/tests/core/blocks/hero.js b/cypress/tests/core/blocks/hero.js index d93012c22b..445dbc13f3 100644 --- a/cypress/tests/core/blocks/hero.js +++ b/cypress/tests/core/blocks/hero.js @@ -29,10 +29,7 @@ describe('Blocks Tests', () => { cy.navigate('/my-page/edit'); cy.wait('@schema'); - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .hero').contains('Hero').click(); + cy.addNewBlock('hero'); // cy.fixture(expectedFile).then(fileContent => { // cy.get(`.block.${block} [data-cy="dropzone]`).upload( diff --git a/cypress/tests/coresandbox/listingblock.js b/cypress/tests/coresandbox/listingblock.js index 01a665e515..82fe619aec 100644 --- a/cypress/tests/coresandbox/listingblock.js +++ b/cypress/tests/coresandbox/listingblock.js @@ -40,12 +40,7 @@ context('Listing block tests', () => { cy.navigate('/document/edit'); // Add listing block - cy.getSlate().click(); - cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Common').click(); - cy.get('.blocks-chooser .common') - .contains('Listing') - .click({ force: true }); + cy.addNewBlock('listing'); // select variation cy.get('#field-variation') @@ -90,6 +85,7 @@ context('Listing block tests', () => { cy.navigate('/document/edit'); // Add listing block + cy.addNewBlock('listing'); cy.getSlate().click(); cy.get('button.block-add-button').click(); cy.get('.blocks-chooser .title').contains('Common').click(); diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 3c4cc5a11c..30cb937c14 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -47,6 +47,17 @@ Volto 17 now uses Webpack 5. If you customized `razzle.config.js` for your project to change Webpack configuration or use Webpack plugins, you might need to make adjustments. +### `BlockChooser` component now uses `popperjs` internally + +Technically not a breaking, the API nor the component contract has changed, but it's worth noting this change in here. + +```{versionadded} 17.0.0-alpha.1 +The `BlockChooser` component now uses `popperjs` library to position itself in the screen. +It spawns at the end of the body instead of inner the block that called it. +This is better from the UI point of view, since any other element can take precedence in the CSS element flow, preventing the block chooser to get overlapped by anything else. +``` + +If you have customized the `BlockChooser` in any way could be that this now could interact with your customizations. (volto-upgrade-guide-16.x.x)= diff --git a/news/4141.feature b/news/4141.feature new file mode 100644 index 0000000000..04f470f52c --- /dev/null +++ b/news/4141.feature @@ -0,0 +1 @@ +Use popperjs in BlockChooser, move the markup to the bottom of the body tag. @sneridagh diff --git a/src/components/manage/BlockChooser/BlockChooserButton.jsx b/src/components/manage/BlockChooser/BlockChooserButton.jsx index 3943c1ee5f..ae60096c14 100644 --- a/src/components/manage/BlockChooser/BlockChooserButton.jsx +++ b/src/components/manage/BlockChooser/BlockChooserButton.jsx @@ -4,8 +4,10 @@ import addSVG from '@plone/volto/icons/circle-plus.svg'; import { blockHasValue } from '@plone/volto/helpers'; import { Icon, BlockChooser } from '@plone/volto/components'; import config from '@plone/volto/registry'; -import { Button } from 'semantic-ui-react'; +import { Button, Ref } from 'semantic-ui-react'; import { defineMessages, useIntl } from 'react-intl'; +import { usePopper } from 'react-popper'; +import { Portal } from 'react-portal'; const messages = defineMessages({ addBlock: { @@ -76,41 +78,73 @@ const BlockChooserButton = (props) => { }; }, [handleClickOutside]); + const [referenceElement, setReferenceElement] = React.useState(null); + const [popperElement, setPopperElement] = React.useState(null); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: config.experimental.addBlockButton.enabled + ? 'bottom' + : 'right-start', + modifiers: [ + { + name: 'offset', + options: { + offset: [-10, 5], + }, + }, + { + name: 'flip', + options: { + fallbackPlacements: ['right-end', 'top-start'], + }, + }, + ], + }); + return ( <> {!disableNewBlocks && (config.experimental.addBlockButton.enabled || !blockHasValue(data)) && ( - setAddNewBlockOpened(true)} - /> + + setAddNewBlockOpened(true)} + /> + )} {addNewBlockOpened && ( - { - setAddNewBlockOpened(false); - onMutateBlock(id, value); - } - : null - } - onInsertBlock={ - onInsertBlock - ? (id, value) => { - setAddNewBlockOpened(false); - onInsertBlock(id, value); - } - : null - } - currentBlock={block} - allowedBlocks={allowedBlocks} - blocksConfig={blocksConfig} - properties={properties} - showRestricted={showRestricted} - ref={blockChooserRef} - /> + +
    + { + setAddNewBlockOpened(false); + onMutateBlock(id, value); + } + : null + } + onInsertBlock={ + onInsertBlock + ? (id, value) => { + setAddNewBlockOpened(false); + onInsertBlock(id, value); + } + : null + } + currentBlock={block} + allowedBlocks={allowedBlocks} + blocksConfig={blocksConfig} + properties={properties} + showRestricted={showRestricted} + ref={blockChooserRef} + /> +
    +
    )} ); diff --git a/src/components/manage/BlockChooser/BlockChooserSearch.jsx b/src/components/manage/BlockChooser/BlockChooserSearch.jsx index f7b03eeed3..390e240027 100644 --- a/src/components/manage/BlockChooser/BlockChooserSearch.jsx +++ b/src/components/manage/BlockChooser/BlockChooserSearch.jsx @@ -31,7 +31,6 @@ const BlockChooserSearch = ({ onChange, searchValue }) => { autoComplete="off" placeholder={intl.formatMessage(messages.search)} title={intl.formatMessage(messages.search)} - autoFocus ref={searchInput} /> {searchValue && ( diff --git a/theme/themes/pastanaga/extras/blocks.less b/theme/themes/pastanaga/extras/blocks.less index c298319880..1686efffde 100644 --- a/theme/themes/pastanaga/extras/blocks.less +++ b/theme/themes/pastanaga/extras/blocks.less @@ -482,7 +482,6 @@ body.has-toolbar.has-sidebar-collapsed .ui.wrapper > .ui.inner.block.full { border: none !important; background: white !important; border-radius: 50% !important; - transform: translateX(-50%); } &:not(.new-add-block) { @@ -712,20 +711,12 @@ body.has-toolbar.has-sidebar-collapsed .ui.wrapper > .ui.inner.block.full { } .blocks-chooser { - position: absolute; - z-index: 10; - top: 100%; - left: 50%; width: 310px; padding: 4px; background-color: rgba(255, 255, 255, 0.975); border-radius: 2px; box-shadow: 0 0 8px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.05); - &.new-add-block { - transform: translate(-50%, 22px); - } - &:not(.new-add-block) { top: -12px; left: -9px; From 9d03eeaab830ce9d025fbe1b0c419f03bf8f32d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 6 Mar 2023 18:20:39 +0100 Subject: [PATCH 266/326] Improve flaky test in autofocus Cypress tests (#4475) --- cypress/tests/core/blocks/blocks-autofocus.js | 4 ++-- news/4475.bugfix | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/4475.bugfix diff --git a/cypress/tests/core/blocks/blocks-autofocus.js b/cypress/tests/core/blocks/blocks-autofocus.js index a806cc6887..9e9c27be21 100644 --- a/cypress/tests/core/blocks/blocks-autofocus.js +++ b/cypress/tests/core/blocks/blocks-autofocus.js @@ -32,8 +32,8 @@ describe('New Block Auto Focus Tests', () => { it('Press Enter on a text block adds new autofocused default block', () => { cy.getSlate().click(); cy.get('button.block-add-button').click(); - cy.get('.blocks-chooser .title').contains('Text').click(); - cy.get('.blocks-chooser .text').contains('Text').click(); + cy.get('.blocks-chooser .title').contains('Text').click({ force: true }); + cy.get('.blocks-chooser .text').contains('Text').click({ force: true }); cy.get('.text-slate-editor-inner').first().click().type('{enter}'); cy.get('*[class^="block-editor"]') .eq(2) diff --git a/news/4475.bugfix b/news/4475.bugfix new file mode 100644 index 0000000000..ea4f489eed --- /dev/null +++ b/news/4475.bugfix @@ -0,0 +1 @@ +Improve flaky test in autofocus Cypress tests @sneridagh From 507136d0456f1d0b7d61676d441c34ff29351bfd Mon Sep 17 00:00:00 2001 From: Alok Kumar Date: Mon, 6 Mar 2023 23:47:06 +0530 Subject: [PATCH 267/326] Fix order of row of long table in edit and view mode. (#4472) Co-authored-by: David Glick --- news/4473.bugfix | 1 + packages/volto-slate/src/blocks/Table/TableBlockView.jsx | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 news/4473.bugfix diff --git a/news/4473.bugfix b/news/4473.bugfix new file mode 100644 index 0000000000..4314fff4c5 --- /dev/null +++ b/news/4473.bugfix @@ -0,0 +1 @@ +Fix order of row of long table in edit and view mode @iFlameing \ No newline at end of file diff --git a/packages/volto-slate/src/blocks/Table/TableBlockView.jsx b/packages/volto-slate/src/blocks/Table/TableBlockView.jsx index 90d1af0a2a..8ea9c210aa 100644 --- a/packages/volto-slate/src/blocks/Table/TableBlockView.jsx +++ b/packages/volto-slate/src/blocks/Table/TableBlockView.jsx @@ -33,13 +33,13 @@ const View = ({ data }) => { }, [data.table.rows]); const rows = useMemo(() => { - const items = {}; - if (!data.table.rows) return {}; + const items = []; + if (!data.table.rows) return []; data.table.rows.forEach((row, index) => { if (index > 0) { - items[row.key] = []; + items[index] = []; row.cells.forEach((cell, cellIndex) => { - items[row.key][cellIndex] = { + items[index][cellIndex] = { ...cell, value: cell.value && Node.string({ children: cell.value }).length > 0 From 0b81ae20927cd741f67e8b29984e95f7655663d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Tue, 7 Mar 2023 11:05:11 +0100 Subject: [PATCH 268/326] Teaser block docs (#4461) Co-authored-by: Steve Piercy --- docs/source/blocks/block-style-wrapper.md | 4 +- docs/source/blocks/core/index.md | 19 ++ .../{listing-block.md => core/listing.md} | 4 +- docs/source/blocks/core/teaser.md | 175 ++++++++++++++++++ docs/source/blocks/index.md | 2 +- .../configuration/component-registry.md | 13 +- news/4461.documentation | 1 + 7 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 docs/source/blocks/core/index.md rename docs/source/blocks/{listing-block.md => core/listing.md} (97%) create mode 100644 docs/source/blocks/core/teaser.md create mode 100644 news/4461.documentation diff --git a/docs/source/blocks/block-style-wrapper.md b/docs/source/blocks/block-style-wrapper.md index 548d890455..e3e825207e 100644 --- a/docs/source/blocks/block-style-wrapper.md +++ b/docs/source/blocks/block-style-wrapper.md @@ -7,7 +7,9 @@ myst: "keywords": "Volto, Plone, frontend, React, Blocks, Edit, Style, Wrapper, components" --- -# Blocks - Style Wrapper +(block-style-wrapper-label)= + +# Block style wrapper The block style wrapper is part of a block anatomy. It allows you to inject styles from the block schema into the block wrapper in the form of class names. diff --git a/docs/source/blocks/core/index.md b/docs/source/blocks/core/index.md new file mode 100644 index 0000000000..03c2b19613 --- /dev/null +++ b/docs/source/blocks/core/index.md @@ -0,0 +1,19 @@ +--- +myst: + html_meta: + "description": "Core blocks provided in Volto by default" + "property=og:description": "Core blocks provided in Volto by default" + "property=og:title": "Core blocks provided in Volto by default" + "keywords": "Volto, Plone, frontend, React, blocks, default, core" +--- + +# Core Blocks developer notes + +This part of the documentation describes how to develop the core blocks in Volto. + +```{toctree} +:maxdepth: 1 + +listing +teaser +``` diff --git a/docs/source/blocks/listing-block.md b/docs/source/blocks/core/listing.md similarity index 97% rename from docs/source/blocks/listing-block.md rename to docs/source/blocks/core/listing.md index c0f6dbcc44..5472fd9fd2 100644 --- a/docs/source/blocks/listing-block.md +++ b/docs/source/blocks/core/listing.md @@ -7,9 +7,9 @@ myst: "keywords": "Volto, Plone, frontend, React, Upgrade, Guide, Block extensions, variations, schema enhancers, listing block" --- -(extensions-block-extensions-mechanism)= +(listing-block)= -# Listing block extensions +# Listing block The listing block is a special block that can be configured to run a catalog query in the backend and show the results of that search. diff --git a/docs/source/blocks/core/teaser.md b/docs/source/blocks/core/teaser.md new file mode 100644 index 0000000000..352bc151f1 --- /dev/null +++ b/docs/source/blocks/core/teaser.md @@ -0,0 +1,175 @@ +--- +myst: + html_meta: + "description": "Volto Teaser Block description and developer notes" + "property=og:description": "Volto Teaser Block description and developer notes" + "property=og:title": "Volto Teaser Block" + "keywords": "Volto, React, blocks, teaser, Plone" +--- + +(teaser-block)= + +# Teaser Block + +```{versionadded} Volto 16.14.0 +``` + +Volto comes with a Teaser block, which when given a content object as a target, is able to pull information from the targeted content object and show it in the block. +By default, this information includes the `title`, `description`, `head_title`, and `preview_image` fields. +It shows them in a summary layout, with the image on the left, and the head title, title, and description on the right in order from top to bottom. + +## Settings + +The Teaser has the following settings: + +Target +: The target is either an existing content item in your Plone site, or an external URL + +Title +: The title is the title of the Teaser block. +By default is copied over from the Teaser target. + +Head title +: The head title is a heading that appears above the title of the Teaser Block. +By default is copied over from the Teaser target. + +Description +: The description is plain text that summarizes or describes the Teaser. +By default is copied over from the Teaser target. + +Image override +: The image override is either an existing image in your Plone site, or an external URL. +It overrides the image provided by the Teaser target. + +## Style wrapper properties + +The Teaser has a styling property to align the image. +The image can be aligned to the left (default), right, or top. + +The styling wrapper properties can be extended using the {ref}`block-style-wrapper-label`. + +## Variations + +A Teaser block has a default variation. +A variation can be extended using {ref}`extensions-block-variations`. + +## Data adapter + +The Teaser has a data adapter function that allows you to both tap into the changes in the settings and issue changes in other settings fields. +This is valuable in the Teaser block because it saves an internal cache of the target element. +If you select the target, these values are updated. +When you update the target, by default these values remain, but you can issue another behavior. + +The data adapter function is defined in the block's setting `dataAdapter`. +You can override it and add your own function, if required. +The following is the default adapter. +You should stick to this signature in your custom adapters. + +```js +import { isEmpty } from 'lodash'; + +export const TeaserBlockDataAdapter = ({ + block, + data, + id, + onChangeBlock, + value, +}) => { + let dataSaved = { + ...data, + [id]: value, + }; + if (id === 'href' && !isEmpty(value) && !data.title && !data.description) { + dataSaved = { + ...dataSaved, + title: value[0].Title, + description: value[0].Description, + head_title: value[0].head_title, + }; + } + onChangeBlock(block, dataSaved); +}; +``` + +## Custom components in registry + +The Teaser block looks up a couple of components in the {ref}`component-registry` that you can provide in case you want to modify the default behavior. + +### Image component + +The image component used in the Teaser can be overridden using your own custom image component. +By default, it uses an `` tag with the following signature. + +```jsx + +``` + +You can register your as: + +```js + import Image from './components/Image/MyImageComponent'; + + ... + + config.registerComponent({ + name: 'Image', + component: Image, + }); +``` + +### Content type dependent `Body` component + +The Teaser component supports specifying a custom component depending on the target content type. + +You can provide your own component, registering a component, for example, in your add-on configuration as shown below. + +```js +import NewsItem from './components/Blocks/Teaser/NewsItem'; + +... + + config.registerComponent({ + name: 'Teaser', + component: NewsItem, + dependencies: 'News Item', + }); +``` + +The `name` must be `Teaser`, but then as a dependency you must provide the name of the content type. + +After this, if you select a content type `News Item` as the target of the teaser, the `Body` component used will be `NewsItem` instead of the default one. + +## Custom `Body` component + +The Teaser body provides a default `Body` component that can be overridden. +Although it can be done via component shadowing, it is recommended that you override it using the `variations` block setting field. + +```js +import CustomTeaserBlockDefaultBody from './components/Blocks/Teaser'; + +... + + variations: [ + { + id: 'default', + isDefault: true, + title: 'Default', + template: CustomTeaserBlockDefaultBody, + }, + ], +``` + +The custom content type `Body` component mentioned in the previous section is compatible with this customization. +In this case, the content type customization will take precedence, if it matches with the content type of the target. + +## Migration from `volto-blocks-grid` + +From Volto 16.14.0 the Teaser block, as it's in the `volto-blocks-grid` add-on version 7.0.0, is included in Volto core. + +Because of the add-on configuration priority, if you have `volto-blocks-grid` installed in your project, the Teaser block from the add-on (and your project amendments to the configuration, if any) will be applied and used. + +In case you want to use the core Teaser block, you need to re-apply the core configuration for the Teaser block in your project or add-ons. diff --git a/docs/source/blocks/index.md b/docs/source/blocks/index.md index 2f4d427f1f..2a8e485349 100644 --- a/docs/source/blocks/index.md +++ b/docs/source/blocks/index.md @@ -18,6 +18,6 @@ settings editcomponent block-style-wrapper extensions -listing-block ssr +core/index ``` diff --git a/docs/source/configuration/component-registry.md b/docs/source/configuration/component-registry.md index 6dfe49a45a..1043115cdc 100644 --- a/docs/source/configuration/component-registry.md +++ b/docs/source/configuration/component-registry.md @@ -1,4 +1,15 @@ -# Component Registry +--- +myst: + html_meta: + "description": "Volto provides an integrated component registry that stores named references to components, allowing them to be queried programmatically." + "property=og:description": "Volto provides an integrated component registry that stores named references to components, allowing them to be queried programmatically." + "property=og:title": "Component registry in Volto" + "keywords": "Volto, Plone, frontend, React, registry, component" +--- + +(component-registry)= + +# Component registry The {term}`configuration registry` has a component registry integrated on itself. These registry stores by a given name the components. diff --git a/news/4461.documentation b/news/4461.documentation new file mode 100644 index 0000000000..cef8c89618 --- /dev/null +++ b/news/4461.documentation @@ -0,0 +1 @@ +Complete teaser docs, add new section in `Blocks`: `Core Blocks developers notes` @sneridagh From 12e05f1013498885884ad32521da1008e80858bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 8 Mar 2023 12:23:02 +0100 Subject: [PATCH 269/326] Fix history view dropdown for first entry, showing 'Revert to this version option' always (#4471) --- news/4471.bugfix | 1 + src/components/manage/History/History.jsx | 53 +++++++++++++++-------- 2 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 news/4471.bugfix diff --git a/news/4471.bugfix b/news/4471.bugfix new file mode 100644 index 0000000000..eec2978aa9 --- /dev/null +++ b/news/4471.bugfix @@ -0,0 +1 @@ +Fix history view dropdown for first entry, showing 'Revert to this version option' always @sneridagh diff --git a/src/components/manage/History/History.jsx b/src/components/manage/History/History.jsx index a0a638bfb3..fb420bbadb 100644 --- a/src/components/manage/History/History.jsx +++ b/src/components/manage/History/History.jsx @@ -116,22 +116,37 @@ class History extends Component { this.props.revertHistory(getBaseUrl(this.props.pathname), value); } - /** - * Render method. - * @method render - * @returns {string} Markup for the component. - */ - render() { + processHistoryEntries = () => { + // Getting the history entries from the props + // No clue why the reverse(concat()) is necessary const entries = reverse(concat(this.props.entries)); let title = entries.length > 0 ? entries[0].state_title : ''; for (let x = 1; x < entries.length; x += 1) { entries[x].prev_state_title = title; title = entries[x].state_title || title; } + // We reverse them again reverse(entries); + + // We identify the latest 'versioning' entry and mark it + const current_version = find(entries, (item) => item.type === 'versioning'); + if (current_version) { + current_version.is_current = true; + } + return entries; + }; + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { const historyAction = find(this.props.objectActions, { id: 'history', }); + const entries = this.processHistoryEntries(); + return !historyAction ? ( <> {this.props.token ? ( @@ -266,18 +281,20 @@ class History extends Component { /> )} - {'version' in entry && ( - - {' '} - - - )} + {'version' in entry && + entry.may_revert && + !entry.is_current && ( + + {' '} + + + )} )} From 90c72c6ec33b59ffc739269f80894de3ba098716 Mon Sep 17 00:00:00 2001 From: Nicola Zambello Date: Wed, 8 Mar 2023 12:26:28 +0100 Subject: [PATCH 270/326] fix: newsitem view blocks wrapper className (#4443) Co-authored-by: David Glick --- news/4443.bugfix | 1 + src/components/theme/View/EventView.jsx | 2 +- src/components/theme/View/NewsItemView.jsx | 2 +- .../theme/View/__snapshots__/EventView.test.jsx.snap | 6 +++--- 4 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 news/4443.bugfix diff --git a/news/4443.bugfix b/news/4443.bugfix new file mode 100644 index 0000000000..ba1f1a8c8a --- /dev/null +++ b/news/4443.bugfix @@ -0,0 +1 @@ +fix: newsitem and event views wrapper classNames @nzambello diff --git a/src/components/theme/View/EventView.jsx b/src/components/theme/View/EventView.jsx index 7d44d33cde..3a82075e00 100644 --- a/src/components/theme/View/EventView.jsx +++ b/src/components/theme/View/EventView.jsx @@ -43,7 +43,7 @@ const EventView = (props) => { const { content } = props; return ( -
    +
    {hasBlocksData(content) ? ( diff --git a/src/components/theme/View/NewsItemView.jsx b/src/components/theme/View/NewsItemView.jsx index 6415dda53f..0e16994429 100644 --- a/src/components/theme/View/NewsItemView.jsx +++ b/src/components/theme/View/NewsItemView.jsx @@ -21,7 +21,7 @@ import RenderBlocks from '@plone/volto/components/theme/View/RenderBlocks'; */ const NewsItemView = ({ content }) => hasBlocksData(content) ? ( -
    +
    ) : ( diff --git a/src/components/theme/View/__snapshots__/EventView.test.jsx.snap b/src/components/theme/View/__snapshots__/EventView.test.jsx.snap index 4bd487acd5..acf53acafc 100644 --- a/src/components/theme/View/__snapshots__/EventView.test.jsx.snap +++ b/src/components/theme/View/__snapshots__/EventView.test.jsx.snap @@ -2,7 +2,7 @@ exports[`renders an event view component with all props 1`] = `
    Date: Wed, 8 Mar 2023 03:38:35 -0800 Subject: [PATCH 271/326] Improvements to dev proxy (#4434) --- news/4434.feature | 8 ++++++++ src/config/index.js | 1 + src/express-middleware/devproxy.js | 6 ++++-- src/server.jsx | 1 + src/start-server.js | 6 ++++-- 5 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 news/4434.feature diff --git a/news/4434.feature b/news/4434.feature new file mode 100644 index 0000000000..0f5f181a52 --- /dev/null +++ b/news/4434.feature @@ -0,0 +1,8 @@ +Improvements to the dev API proxy: +- Prefer RAZZLE_INTERNAL_API_PATH over RAZZLE_API_PATH as the target of the proxy. + The target of the API proxy is now always logged on startup, even in production mode. +- Support proxying to a backend served over https. For this configuration it + might be necessary to set RAZZLE_DEV_PROXY_INSECURE=1 if the backend + certificate can't be verified. + +[davisagli] diff --git a/src/config/index.js b/src/config/index.js index a4dee4f99d..60985f5e26 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -89,6 +89,7 @@ let config = { // https://6.docs.plone.org/volto/deploying/seamless-mode.html devProxyToApiPath: process.env.RAZZLE_DEV_PROXY_API_PATH || + process.env.RAZZLE_INTERNAL_API_PATH || process.env.RAZZLE_API_PATH || 'http://localhost:8080/Plone', // Set it to '' for disabling the proxy // proxyRewriteTarget Set it for set a custom target for the proxy or overide the internal VHM rewrite diff --git a/src/express-middleware/devproxy.js b/src/express-middleware/devproxy.js index c2f2142d6b..d3411fdf89 100644 --- a/src/express-middleware/devproxy.js +++ b/src/express-middleware/devproxy.js @@ -75,12 +75,14 @@ export default function () { const { apiPathURL, instancePath } = getEnv(); const target = config.settings.proxyRewriteTarget || - `/VirtualHostBase/http/${apiPathURL.hostname}:${apiPathURL.port}${instancePath}/++api++/VirtualHostRoot`; + `/VirtualHostBase/${apiPathURL.protocol.slice(0, -1)}/${ + apiPathURL.hostname + }:${apiPathURL.port}${instancePath}/++api++/VirtualHostRoot`; return `${target}${path.replace('/++api++', '')}`; }, logLevel: process.env.DEBUG_HPM ? 'debug' : 'silent', - ...(config.settings?.proxyRewriteTarget?.startsWith('https') && { + ...(process.env.RAZZLE_DEV_PROXY_INSECURE && { changeOrigin: true, secure: false, }), diff --git a/src/server.jsx b/src/server.jsx index df5290aa5d..8fea05fd78 100644 --- a/src/server.jsx +++ b/src/server.jsx @@ -329,6 +329,7 @@ export const defaultReadCriticalCss = () => { // Exposed for the console bootstrap info messages server.apiPath = config.settings.apiPath; server.devProxyToApiPath = config.settings.devProxyToApiPath; +server.proxyRewriteTarget = config.settings.proxyRewriteTarget; server.publicURL = config.settings.publicURL; export default server; diff --git a/src/start-server.js b/src/start-server.js index 5ea7ea0bb4..ee34673e29 100644 --- a/src/start-server.js +++ b/src/start-server.js @@ -19,9 +19,11 @@ export default () => { } else { console.log(`API server (API_PATH) is set to: ${app.apiPath}`); } - if (__DEVELOPMENT__ && app.devProxyToApiPath) + if (app.devProxyToApiPath) console.log( - `Using internal proxy: ${app.publicURL} -> ${app.devProxyToApiPath}`, + `Proxying API requests from ${app.publicURL}/++api++ to ${ + app.devProxyToApiPath + }${app.proxyRewriteTarget || ''}`, ); console.log(`🎭 Volto started at ${bind_address}:${port} 🚀`); From a4766a7e6d2029d23c221f29c4c8536e5803929a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 8 Mar 2023 15:20:12 +0100 Subject: [PATCH 272/326] Release 16.15.0. Update Changelog. (#4485) --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08a327a59e..04920603d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,31 @@ - Use a universal static path for both documentation and volto repos. @stevepiercy [#4376](https://github.com/plone/volto/issues/4376) +## 16.15.0 (2023-03-08) + +### Feature + +- Improvements to the dev API proxy: + - Prefer RAZZLE_INTERNAL_API_PATH over RAZZLE_API_PATH as the target of the proxy. + The target of the API proxy is now always logged on startup, even in production mode. + - Support proxying to a backend served over https. For this configuration it + might be necessary to set RAZZLE_DEV_PROXY_INSECURE=1 if the backend + certificate can't be verified. + + [davisagli] [#4434](https://github.com/plone/volto/issues/4434) + +### Bugfix + +- fix: newsitem and event views wrapper classNames @nzambello [#4443](https://github.com/plone/volto/issues/4443) +- Fix weird GHA failure on config option not supported @sneridagh [#4466](https://github.com/plone/volto/issues/4466) +- Fix history view dropdown for first entry, showing 'Revert to this version option' always @sneridagh [#4471](https://github.com/plone/volto/issues/4471) +- Fix order of row of long table in edit and view mode @iFlameing [#4473](https://github.com/plone/volto/issues/4473) + +### Documentation + +- Complete teaser docs, add new section in `Blocks`: `Core Blocks developers notes` @sneridagh [#4461](https://github.com/plone/volto/issues/4461) + + ## 16.14.0 (2023-03-03) ### Feature From 3ea85278c8145f215854eb925d8851a51f2f2771 Mon Sep 17 00:00:00 2001 From: Mauro Amico Date: Thu, 9 Mar 2023 10:54:00 +0100 Subject: [PATCH 273/326] Serve static assets with an efficient cache policy (#2216) Co-authored-by: nzambello Co-authored-by: Tiberiu Ichim Co-authored-by: Tiberiu Ichim --- CHANGELOG.md | 3 +++ src/config/server.js | 19 +++++++++++++++++++ src/express-middleware/static.js | 32 ++++++++++++++++++++++++++++++++ src/server.jsx | 7 ------- 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 src/express-middleware/static.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 04920603d3..a7e84ec14a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1115,6 +1115,9 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa ### Breaking +- Add directive to cache stable resources in browser or intermediate server for 365 days, + static resource that could change after a new deployment for 1 minute. @mamico + ### Feature - Added new components `Aliases` for aliases control in Volto. Alias management in both controlpanel and object view. @andreiggr @avoinea diff --git a/src/config/server.js b/src/config/server.js index 1698da1233..8e8d7e4b61 100644 --- a/src/config/server.js +++ b/src/config/server.js @@ -2,6 +2,7 @@ import imagesMiddleware from '@plone/volto/express-middleware/images'; import filesMiddleware from '@plone/volto/express-middleware/files'; import robotstxtMiddleware from '@plone/volto/express-middleware/robotstxt'; import sitemapMiddleware from '@plone/volto/express-middleware/sitemap'; +import staticsMiddleware from '@plone/volto/express-middleware/static'; import devProxyMiddleware from '@plone/volto/express-middleware/devproxy'; const settings = { @@ -11,10 +12,28 @@ const settings = { imagesMiddleware(), robotstxtMiddleware(), sitemapMiddleware(), + staticsMiddleware(), ], criticalCssPath: 'public/critical.css', readCriticalCss: null, // so it will be defaultReadCriticalCss extractScripts: { errorPages: false }, + staticFiles: [ + { + id: 'root_static', + match: /^\/static\/.*/, + headers: { + // stable resources never change. 31536000 seconds == 365 days + 'Cache-Control': 'public, max-age=31536000', + }, + }, + { + id: 'all', + match: /.*/, + headers: { + 'Cache-Control': 'public, max-age=60', + }, + }, + ], }; export default settings; diff --git a/src/express-middleware/static.js b/src/express-middleware/static.js new file mode 100644 index 0000000000..5e90754bc0 --- /dev/null +++ b/src/express-middleware/static.js @@ -0,0 +1,32 @@ +import express from 'express'; +import path from 'path'; +import config from '@plone/volto/registry'; + +const staticMiddleware = express.static( + process.env.BUILD_DIR + ? path.join(process.env.BUILD_DIR, 'public') + : process.env.RAZZLE_PUBLIC_DIR, + { + setHeaders: function (res, path) { + const pathLib = require('path'); + const base = pathLib.resolve(process.env.RAZZLE_PUBLIC_DIR); + const relpath = path.substr(base.length); + config.settings.serverConfig.staticFiles.some((elem) => { + if (relpath.match(elem.match)) { + for (const name in elem.headers) { + res.setHeader(name, elem.headers[name] || 'undefined'); + } + return true; + } + return false; + }); + }, + }, +); + +export default function () { + const middleware = express.Router(); + middleware.all('*', staticMiddleware); + middleware.id = 'staticResourcesProcessor'; + return middleware; +} diff --git a/src/server.jsx b/src/server.jsx index 8fea05fd78..a9858b9854 100644 --- a/src/server.jsx +++ b/src/server.jsx @@ -59,13 +59,6 @@ const supported = new locale.Locales(keys(languages), 'en'); const server = express() .disable('x-powered-by') - .use( - express.static( - process.env.BUILD_DIR - ? path.join(process.env.BUILD_DIR, 'public') - : process.env.RAZZLE_PUBLIC_DIR, - ), - ) .head('/*', function (req, res) { // Support for HEAD requests. Required by start-test utility in CI. res.send(''); From 176aa96cd697530cc4f36b09df35a89b72131614 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Thu, 9 Mar 2023 11:01:21 +0100 Subject: [PATCH 274/326] Changelog --- CHANGELOG.md | 5 ----- news/2216.feature | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) create mode 100644 news/2216.feature diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e84ec14a..4d01600c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1113,11 +1113,6 @@ See https://6.dev-docs.plone.org/volto/upgrade-guide/index.html for more informa ## 16.0.0-alpha.34 (2022-09-17) -### Breaking - -- Add directive to cache stable resources in browser or intermediate server for 365 days, - static resource that could change after a new deployment for 1 minute. @mamico - ### Feature - Added new components `Aliases` for aliases control in Volto. Alias management in both controlpanel and object view. @andreiggr @avoinea diff --git a/news/2216.feature b/news/2216.feature new file mode 100644 index 0000000000..19ee35ab03 --- /dev/null +++ b/news/2216.feature @@ -0,0 +1 @@ +- Add directive to cache stable resources in browser or intermediate server for 365 days by default directly in the SSR Express server, static resource that could change after a new deployment for 1 minute. @mamico From 636149b2582dbb3492820b6a2f67eff53f84f967 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Thu, 9 Mar 2023 16:24:17 +0100 Subject: [PATCH 275/326] Release 17.0.0-alpha.1 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ news/2216.feature | 1 - news/4141.feature | 1 - news/4434.feature | 8 -------- news/4443.bugfix | 1 - news/4461.documentation | 1 - news/4466.bugfix | 1 - news/4470.documentation | 1 - news/4471.bugfix | 1 - news/4473.bugfix | 1 - news/4475.bugfix | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 13 files changed, 31 insertions(+), 19 deletions(-) delete mode 100644 news/2216.feature delete mode 100644 news/4141.feature delete mode 100644 news/4434.feature delete mode 100644 news/4443.bugfix delete mode 100644 news/4461.documentation delete mode 100644 news/4466.bugfix delete mode 100644 news/4470.documentation delete mode 100644 news/4471.bugfix delete mode 100644 news/4473.bugfix delete mode 100644 news/4475.bugfix diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d01600c40..641578d18e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,35 @@ +## 17.0.0-alpha.1 (2023-03-09) + +### Feature + +- - Add directive to cache stable resources in browser or intermediate server for 365 days by default directly in the SSR Express server, static resource that could change after a new deployment for 1 minute. @mamico [#2216](https://github.com/plone/volto/issues/2216) +- Use popperjs in BlockChooser, move the markup to the bottom of the body tag. @sneridagh [#4141](https://github.com/plone/volto/issues/4141) +- Improvements to the dev API proxy: + - Prefer RAZZLE_INTERNAL_API_PATH over RAZZLE_API_PATH as the target of the proxy. + The target of the API proxy is now always logged on startup, even in production mode. + - Support proxying to a backend served over https. For this configuration it + might be necessary to set RAZZLE_DEV_PROXY_INSECURE=1 if the backend + certificate can't be verified. + + [davisagli] [#4434](https://github.com/plone/volto/issues/4434) + +### Bugfix + +- fix: newsitem and event views wrapper classNames @nzambello [#4443](https://github.com/plone/volto/issues/4443) +- Fix weird GHA failure on config option not supported @sneridagh [#4466](https://github.com/plone/volto/issues/4466) +- Fix history view dropdown for first entry, showing 'Revert to this version option' always @sneridagh [#4471](https://github.com/plone/volto/issues/4471) +- Fix order of row of long table in edit and view mode @iFlameing [#4473](https://github.com/plone/volto/issues/4473) +- Improve flaky test in autofocus Cypress tests @sneridagh [#4475](https://github.com/plone/volto/issues/4475) + +### Documentation + +- Complete teaser docs, add new section in `Blocks`: `Core Blocks developers notes` @sneridagh [#4461](https://github.com/plone/volto/issues/4461) +- Change from links to inline literals in `CHANGELOG.md` to fix linkcheckbroken. @stevepiercy [#4470](https://github.com/plone/volto/issues/4470) + + ## 17.0.0-alpha.0 (2023-03-04) ### Breaking diff --git a/news/2216.feature b/news/2216.feature deleted file mode 100644 index 19ee35ab03..0000000000 --- a/news/2216.feature +++ /dev/null @@ -1 +0,0 @@ -- Add directive to cache stable resources in browser or intermediate server for 365 days by default directly in the SSR Express server, static resource that could change after a new deployment for 1 minute. @mamico diff --git a/news/4141.feature b/news/4141.feature deleted file mode 100644 index 04f470f52c..0000000000 --- a/news/4141.feature +++ /dev/null @@ -1 +0,0 @@ -Use popperjs in BlockChooser, move the markup to the bottom of the body tag. @sneridagh diff --git a/news/4434.feature b/news/4434.feature deleted file mode 100644 index 0f5f181a52..0000000000 --- a/news/4434.feature +++ /dev/null @@ -1,8 +0,0 @@ -Improvements to the dev API proxy: -- Prefer RAZZLE_INTERNAL_API_PATH over RAZZLE_API_PATH as the target of the proxy. - The target of the API proxy is now always logged on startup, even in production mode. -- Support proxying to a backend served over https. For this configuration it - might be necessary to set RAZZLE_DEV_PROXY_INSECURE=1 if the backend - certificate can't be verified. - -[davisagli] diff --git a/news/4443.bugfix b/news/4443.bugfix deleted file mode 100644 index ba1f1a8c8a..0000000000 --- a/news/4443.bugfix +++ /dev/null @@ -1 +0,0 @@ -fix: newsitem and event views wrapper classNames @nzambello diff --git a/news/4461.documentation b/news/4461.documentation deleted file mode 100644 index cef8c89618..0000000000 --- a/news/4461.documentation +++ /dev/null @@ -1 +0,0 @@ -Complete teaser docs, add new section in `Blocks`: `Core Blocks developers notes` @sneridagh diff --git a/news/4466.bugfix b/news/4466.bugfix deleted file mode 100644 index 2facedcb3b..0000000000 --- a/news/4466.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix weird GHA failure on config option not supported @sneridagh diff --git a/news/4470.documentation b/news/4470.documentation deleted file mode 100644 index 930a5b495d..0000000000 --- a/news/4470.documentation +++ /dev/null @@ -1 +0,0 @@ -Change from links to inline literals in `CHANGELOG.md` to fix linkcheckbroken. @stevepiercy diff --git a/news/4471.bugfix b/news/4471.bugfix deleted file mode 100644 index eec2978aa9..0000000000 --- a/news/4471.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix history view dropdown for first entry, showing 'Revert to this version option' always @sneridagh diff --git a/news/4473.bugfix b/news/4473.bugfix deleted file mode 100644 index 4314fff4c5..0000000000 --- a/news/4473.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix order of row of long table in edit and view mode @iFlameing \ No newline at end of file diff --git a/news/4475.bugfix b/news/4475.bugfix deleted file mode 100644 index ea4f489eed..0000000000 --- a/news/4475.bugfix +++ /dev/null @@ -1 +0,0 @@ -Improve flaky test in autofocus Cypress tests @sneridagh diff --git a/package.json b/package.json index 16d320e489..1029401f03 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.0", + "version": "17.0.0-alpha.1", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json index 00e9108281..e4ad38ef6e 100644 --- a/packages/volto-slate/package.json +++ b/packages/volto-slate/package.json @@ -1,6 +1,6 @@ { "name": "@plone/volto-slate", - "version": "17.0.0-alpha.0", + "version": "17.0.0-alpha.1", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From 7e8ac32115c7defaa2e3ac50e6240412b0aac114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20S=C3=BCss?= Date: Sat, 11 Mar 2023 09:53:20 +0100 Subject: [PATCH 276/326] Fix training urls (Remove /5/) (#4502) --- CHANGELOG.md | 2 +- README.md | 8 ++++---- api/README.rst | 2 +- docs/source/getting-started/others.md | 8 ++++---- news/4502.documentation | 1 + 5 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 news/4502.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 641578d18e..02e9f73734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ diff --git a/README.md b/README.md index c76079a98f..8cdca13cbe 100644 --- a/README.md +++ b/README.md @@ -190,14 +190,14 @@ On the [Plone Trainings Website](https://training.plone.org) you'll find Volto-dedicated open training materials, plus React and other JavaScript-centered trainings. -- [Mastering Plone 6 Development](https://training.plone.org/5/mastering-plone/) +- [Mastering Plone 6 Development](https://training.plone.org/mastering-plone/) The comprehensive training on Plone 6 with best practice tips for developers and integrators. - [Volto](https://training.plone.org/5/volto/index.html) A detailed training on how to create your own website using Volto frontend. -- [Volto Hands-On](https://training.plone.org/5/voltohandson/index.html) -- [Volto Add-ons Development](https://training.plone.org/5/voltoaddons/index.html) +- [Volto Hands-On](https://training.plone.org/voltohandson/index.html) +- [Volto Add-ons Development](https://training.plone.org/voltoaddons/index.html) - [Plone Deployment](https://training.plone.org/5/plone-deployment/index.html) -- [React](https://training.plone.org/5/react/index.html) +- [React](https://training.plone.org/react/index.html) - [JavaScript For Plone Developers](https://training.plone.org/5/javascript/index.html) ## Talks diff --git a/api/README.rst b/api/README.rst index c249527cbc..3203aea283 100644 --- a/api/README.rst +++ b/api/README.rst @@ -45,7 +45,7 @@ are required for cryptography, image handling/scaling support and compression. You can find more up to date details on installing these necessary libraries for linux systems in the 'Mastering Plone' training at --> https://training.plone.org/5/mastering-plone/installation.html#installing-plone-backend +-> https://training.plone.org/mastering-plone/installation.html#installing-plone-backend For Windows you could try WSL (Windows subsystem for Linux) and for Mac OS X for example Homebrew to install these necessary support libraries. diff --git a/docs/source/getting-started/others.md b/docs/source/getting-started/others.md index da86287873..3b5fcf48ea 100644 --- a/docs/source/getting-started/others.md +++ b/docs/source/getting-started/others.md @@ -15,14 +15,14 @@ On the [Plone Trainings Website](https://training.plone.org) you'll find Volto-dedicated open training materials plus React and other Javascript-centered trainings. -- [Mastering Plone 6 Development](https://training.plone.org/5/mastering-plone/) +- [Mastering Plone 6 Development](https://training.plone.org/mastering-plone/) The comprehensive training on Plone 6 with best practice tips for developers and integrators. - [Volto](https://training.plone.org/5/volto/index.html) A detailed training on how to create your own website using Volto frontend. -- [Volto Hands-On](https://training.plone.org/5/voltohandson/index.html) -- [Volto Add-ons Development](https://training.plone.org/5/voltoaddons/index.html) +- [Volto Hands-On](https://training.plone.org/voltohandson/index.html) +- [Volto Add-ons Development](https://training.plone.org/voltoaddons/index.html) - [Plone Deployment](https://training.plone.org/5/plone-deployment/index.html) -- [React](https://training.plone.org/5/react/index.html) +- [React](https://training.plone.org/react/index.html) - [JavaScript For Plone Developers](https://training.plone.org/5/javascript/index.html) ## How does it work under the hood diff --git a/news/4502.documentation b/news/4502.documentation new file mode 100644 index 0000000000..cc20ee1c42 --- /dev/null +++ b/news/4502.documentation @@ -0,0 +1 @@ +Fix training urls @ksuess \ No newline at end of file From b5bafd5b2be339539f1a384dbe7137235b2506ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20S=C3=BCss?= Date: Tue, 14 Mar 2023 11:01:00 +0100 Subject: [PATCH 277/326] Mark empty listings for optional hiding, per variation or globally (#4474) --- news/4393.feature | 1 + src/components/manage/Blocks/Listing/Edit.jsx | 5 - .../manage/Blocks/Listing/ListingBody.jsx | 138 ++++++++++-------- src/components/manage/Blocks/Listing/View.jsx | 4 - .../__snapshots__/ListingBody.test.jsx.snap | 4 +- 5 files changed, 81 insertions(+), 71 deletions(-) create mode 100644 news/4393.feature diff --git a/news/4393.feature b/news/4393.feature new file mode 100644 index 0000000000..977e5aec23 --- /dev/null +++ b/news/4393.feature @@ -0,0 +1 @@ +Add option to hide empty listing blocks @ksuess \ No newline at end of file diff --git a/src/components/manage/Blocks/Listing/Edit.jsx b/src/components/manage/Blocks/Listing/Edit.jsx index 9f3d632b75..f447211415 100644 --- a/src/components/manage/Blocks/Listing/Edit.jsx +++ b/src/components/manage/Blocks/Listing/Edit.jsx @@ -47,14 +47,9 @@ const Edit = React.memo( ? intl.formatMessage(messages.results) : intl.formatMessage(messages.items)); - const HeadlineTag = data.headlineTag || 'h2'; - return ( <>

    {placeholder}

    - {data.headline && ( - {data.headline} - )} { ? variation.noResultsComponent : config.blocks?.blocksConfig['listing'].noResultsComponent; - return listingItems?.length > 0 ? ( -
    - - {totalPages > 1 && ( -
    - { - !isEditMode && - listingRef.current.scrollIntoView({ behavior: 'smooth' }); - onPaginationChange(e, { activePage }); - }} - firstItem={null} - lastItem={null} - prevItem={{ - content: , - icon: true, - 'aria-disabled': !prevBatch, - className: !prevBatch ? 'disabled' : null, - }} - nextItem={{ - content: , - icon: true, - 'aria-disabled': !nextBatch, - className: !nextBatch ? 'disabled' : null, - }} + const HeadlineTag = data.headlineTag || 'h2'; + + return ( + <> + {data.headline && ( + 0, + })} + > + {data.headline} + + )} + {listingItems?.length > 0 ? ( +
    + + {totalPages > 1 && ( +
    + { + !isEditMode && + listingRef.current.scrollIntoView({ behavior: 'smooth' }); + onPaginationChange(e, { activePage }); + }} + firstItem={null} + lastItem={null} + prevItem={{ + content: , + icon: true, + 'aria-disabled': !prevBatch, + className: !prevBatch ? 'disabled' : null, + }} + nextItem={{ + content: , + icon: true, + 'aria-disabled': !nextBatch, + className: !nextBatch ? 'disabled' : null, + }} + /> +
    + )} +
    + ) : isEditMode ? ( +
    + {isFolderContentsListing && ( + + )} + {hasLoaded && NoResults && ( + + )} + + + + + +
    + ) : ( +
    + {hasLoaded && NoResults && ( + + )} + + + + +
    )} -
    - ) : isEditMode ? ( -
    - {isFolderContentsListing && ( - - )} - {hasLoaded && NoResults && ( - - )} - - - - - -
    - ) : ( -
    - {hasLoaded && NoResults && ( - - )} - - - - - -
    + ); }); diff --git a/src/components/manage/Blocks/Listing/View.jsx b/src/components/manage/Blocks/Listing/View.jsx index 5c69b38b0d..b70adfdf0b 100644 --- a/src/components/manage/Blocks/Listing/View.jsx +++ b/src/components/manage/Blocks/Listing/View.jsx @@ -7,15 +7,11 @@ import { ListingBlockBody as ListingBody } from '@plone/volto/components'; const View = (props) => { const { data, path, pathname, className } = props; - const HeadlineTag = data.headlineTag || 'h2'; return (
    - {data.headline && ( - {data.headline} - )}
    ); 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..e81db3c83f 100644 --- a/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap +++ b/src/components/manage/Blocks/Listing/__snapshots__/ListingBody.test.jsx.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders a ListingBody component 1`] = ` -
    +
    Date: Tue, 14 Mar 2023 23:28:54 -0600 Subject: [PATCH 278/326] Add hamburger menu animation (#4433) Co-authored-by: David Glick --- news/4433.feature | 1 + package.json | 1 - theme/themes/pastanaga/extras/main.less | 81 ++++++++++++++++++++++++- yarn.lock | 8 --- 4 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 news/4433.feature diff --git a/news/4433.feature b/news/4433.feature new file mode 100644 index 0000000000..c0047b763a --- /dev/null +++ b/news/4433.feature @@ -0,0 +1 @@ +Add custom CSS animation to hamburger menu, to avoid third party dependency. @danalvrz \ No newline at end of file diff --git a/package.json b/package.json index 1029401f03..3d31a28ee5 100644 --- a/package.json +++ b/package.json @@ -290,7 +290,6 @@ "express": "4.17.3", "filesize": "6", "glob": "7.1.6", - "hamburgers": "1.1.3", "history": "4.10.1", "hoist-non-react-statics": "3.3.2", "html-webpack-plugin": "5.5.0", diff --git a/theme/themes/pastanaga/extras/main.less b/theme/themes/pastanaga/extras/main.less index 6aeb31658c..4d294b67ea 100644 --- a/theme/themes/pastanaga/extras/main.less +++ b/theme/themes/pastanaga/extras/main.less @@ -8,7 +8,6 @@ @import (multiple) '../../theme.config'; // Extras (third party libs) -@import (less) '~hamburgers/dist/hamburgers.css'; @import (less) '~react-toastify/dist/ReactToastify.css'; // Mixins @@ -494,6 +493,86 @@ fieldset.invisible { .hamburger-wrapper { position: relative; z-index: 5; + width: 70px; + height: 59px; + padding: 15px 15px; + + .hamburger { + position: relative; + width: 40px; + height: 24px; + padding: 0; + border: none; + background-color: transparent; + cursor: pointer; + transition: 0.3s ease-in-out; + } + + .hamburger::after { + position: absolute; + top: 0; + left: 0; + display: inline-block; + width: 100%; + height: 4px; + background-color: #000; + border-radius: 4px; + content: ''; + transition: 0.3s ease-in-out; + } + + .hamburger::before { + position: absolute; + top: 20px; + left: 0; + display: inline-block; + width: 100%; + height: 4px; + background-color: #000; + border-radius: 4px; + content: ''; + transition: 0.3s ease-in-out; + } + + .hamburger-inner { + position: absolute; + top: 10px; + left: 0; + display: inline-block; + width: 100%; + height: 4px; + border-radius: 4px; + transition: 0.3s ease-in-out; + } + + .hamburger.is-active::after, + .hamburger.is-active::before { + top: 10px; + left: 50%; + width: 0%; + } + + .hamburger-inner::after, + .hamburger-inner::before { + position: absolute; + top: 0; + left: 0; + display: block; + width: 100%; + height: 4px; + background-color: #000; + border-radius: 4px; + content: ''; + transition: 0.3s ease-in-out; + } + + .hamburger.is-active .hamburger-inner::after { + transform: rotate(45deg); + } + + .hamburger.is-active .hamburger-inner::before { + transform: rotate(-45deg); + } } .mobile-menu { diff --git a/yarn.lock b/yarn.lock index b79af047a6..b4f27635da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2700,7 +2700,6 @@ __metadata: filesize: 6 full-icu: 1.4.0 glob: 7.1.6 - hamburgers: 1.1.3 history: 4.10.1 hoist-non-react-statics: 3.3.2 html-webpack-plugin: 5.5.0 @@ -12460,13 +12459,6 @@ __metadata: languageName: node linkType: hard -"hamburgers@npm:1.1.3": - version: 1.1.3 - resolution: "hamburgers@npm:1.1.3" - checksum: 932c1cfa7c8c645d5f8778ee50b762f819be935a4c2275604570cd6043a0850a2f5638714a7ddd4b19d3e2e6ff91e161b08be750f9ce8a43a6d70926ce6a3eff - languageName: node - linkType: hard - "handle-thing@npm:^2.0.0": version: 2.0.1 resolution: "handle-thing@npm:2.0.1" From 70fec9804566dc5de6dd7996652d6b0d5297e014 Mon Sep 17 00:00:00 2001 From: David Glick Date: Wed, 15 Mar 2023 03:22:50 -0700 Subject: [PATCH 279/326] Combined dependency updates (#4520) --- news/4520.bugfix | 1 + package.json | 4 +- packages/generator-volto/package.json | 1 - packages/generator-volto/yarn.lock | 22 ++----- packages/scripts/yarn.lock | 27 +++------ yarn.lock | 82 +++++++-------------------- 6 files changed, 38 insertions(+), 99 deletions(-) create mode 100644 news/4520.bugfix diff --git a/news/4520.bugfix b/news/4520.bugfix new file mode 100644 index 0000000000..19630ab2e3 --- /dev/null +++ b/news/4520.bugfix @@ -0,0 +1 @@ +Patch updates for some dependencies. @davisagli diff --git a/package.json b/package.json index 3d31a28ee5..775e6bd938 100644 --- a/package.json +++ b/package.json @@ -301,7 +301,6 @@ "is-hotkey": "0.2.0", "is-url": "1.2.4", "jest-file": "1.0.0", - "jsonwebtoken": "8.3.0", "jwt-decode": "2.2.0", "less": "3.11.1", "less-loader": "11.1.0", @@ -399,7 +398,7 @@ "universal-cookie-express": "4.0.3", "use-deep-compare-effect": "1.8.1", "uuid": "^8.3.2", - "webpack": "5.75.0", + "webpack": "5.76.1", "webpack-dev-server": "4.11.1", "webpack-node-externals": "3.0.0", "xmlrpc": "1.3.2", @@ -418,6 +417,7 @@ "identity-obj-proxy": "3.0.0", "jest": "26.6.3", "jest-environment-jsdom": "^26", + "jsonwebtoken": "9.0.0", "react-error-overlay": "6.0.9", "react-is": "^16.13.1", "release-it": "^15.1.3", diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index 49f9944101..022f0bc334 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -108,7 +108,6 @@ "fs-extra": "3.0.0", "gitly": "2.0.3", "mkdirp-then": "1.2.0", - "ms": "1.0.0", "mz": "2.6.0", "ora": "1.2.0", "promise": "7.1.1", diff --git a/packages/generator-volto/yarn.lock b/packages/generator-volto/yarn.lock index 9e0129dc37..6e2114304e 100644 --- a/packages/generator-volto/yarn.lock +++ b/packages/generator-volto/yarn.lock @@ -682,7 +682,6 @@ __metadata: jest: ^24.8.0 lint-staged: ^9.4.3 mkdirp-then: 1.2.0 - ms: 1.0.0 mz: 2.6.0 ora: 1.2.0 prettier: 2.0.5 @@ -4150,9 +4149,9 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -5635,13 +5634,11 @@ __metadata: linkType: hard "json5@npm:^2.1.2": - version: 2.1.3 - resolution: "json5@npm:2.1.3" - dependencies: - minimist: ^1.2.5 + version: 2.2.3 + resolution: "json5@npm:2.2.3" bin: json5: lib/cli.js - checksum: b2de57a66520eca0fbb6c5ef59249b8308efb93fe89a8c75f5a6846e4f5f7d99a5a6f2e4db4d7a1c7047802dd816ed602a052d147a415d0e6b7f834885b62bc3 + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 languageName: node linkType: hard @@ -6434,13 +6431,6 @@ __metadata: languageName: node linkType: hard -"ms@npm:1.0.0": - version: 1.0.0 - resolution: "ms@npm:1.0.0" - checksum: d8df74551552ffce335dcb07b77fae25573b6e83789cd58e04c6bd5652095390d55e5be54ec8e143b6dbb785b61615f6a314d5d8e1a141fdf53cf3596d8972b5 - languageName: node - linkType: hard - "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" diff --git a/packages/scripts/yarn.lock b/packages/scripts/yarn.lock index ed87c69c84..1c6cc01244 100644 --- a/packages/scripts/yarn.lock +++ b/packages/scripts/yarn.lock @@ -3244,9 +3244,9 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.0.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -3599,23 +3599,12 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.2": - version: 2.2.0 - resolution: "json5@npm:2.2.0" - dependencies: - minimist: ^1.2.5 - bin: - json5: lib/cli.js - checksum: e88fc5274bb58fc99547baa777886b069d2dd96d9cfc4490b305fd16d711dabd5979e35a4f90873cefbeb552e216b041a304fe56702bedba76e19bc7845f208d - languageName: node - linkType: hard - -"json5@npm:^2.2.1": - version: 2.2.1 - resolution: "json5@npm:2.2.1" +"json5@npm:^2.1.2, json5@npm:^2.2.1": + version: 2.2.3 + resolution: "json5@npm:2.2.3" bin: json5: lib/cli.js - checksum: 74b8a23b102a6f2bf2d224797ae553a75488b5adbaee9c9b6e5ab8b510a2fc6e38f876d4c77dea672d4014a44b2399e15f2051ac2b37b87f74c0c7602003543b + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 languageName: node linkType: hard @@ -3843,7 +3832,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.5": +"minimist@npm:^1.2.0": version: 1.2.6 resolution: "minimist@npm:1.2.6" checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb diff --git a/yarn.lock b/yarn.lock index b4f27635da..6c08fb44f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2713,7 +2713,7 @@ __metadata: jest: 26.6.3 jest-environment-jsdom: ^26 jest-file: 1.0.0 - jsonwebtoken: 8.3.0 + jsonwebtoken: 9.0.0 jwt-decode: 2.2.0 less: 3.11.1 less-loader: 11.1.0 @@ -2816,7 +2816,7 @@ __metadata: use-deep-compare-effect: 1.8.1 use-trace-update: 1.3.2 uuid: ^8.3.2 - webpack: 5.75.0 + webpack: 5.76.1 webpack-dev-server: 4.11.1 webpack-node-externals: 3.0.0 why: 0.6.2 @@ -8335,9 +8335,9 @@ __metadata: linkType: hard "cookiejar@npm:^2.1.0": - version: 2.1.3 - resolution: "cookiejar@npm:2.1.3" - checksum: 88259983ebc52ceb23cdacfa48762b6a518a57872eff1c7ed01d214fff5cf492e2660d7d5c04700a28f1787a76811df39e8639f8e17670b3cf94ecd86e161f07 + version: 2.1.4 + resolution: "cookiejar@npm:2.1.4" + checksum: c4442111963077dc0e5672359956d6556a195d31cbb35b528356ce5f184922b99ac48245ac05ed86cf993f7df157c56da10ab3efdadfed79778a0d9b1b092d5b languageName: node linkType: hard @@ -12971,9 +12971,9 @@ __metadata: linkType: hard "http-cache-semantics@npm:^4.1.0": - version: 4.1.0 - resolution: "http-cache-semantics@npm:4.1.0" - checksum: 974de94a81c5474be07f269f9fd8383e92ebb5a448208223bfb39e172a9dbc26feff250192ecc23b9593b3f92098e010406b0f24bd4d588d631f80214648ed42 + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 languageName: node linkType: hard @@ -15265,20 +15265,15 @@ __metadata: languageName: node linkType: hard -"jsonwebtoken@npm:8.3.0": - version: 8.3.0 - resolution: "jsonwebtoken@npm:8.3.0" +"jsonwebtoken@npm:9.0.0": + version: 9.0.0 + resolution: "jsonwebtoken@npm:9.0.0" dependencies: - jws: ^3.1.5 - lodash.includes: ^4.3.0 - lodash.isboolean: ^3.0.3 - lodash.isinteger: ^4.0.4 - lodash.isnumber: ^3.0.3 - lodash.isplainobject: ^4.0.6 - lodash.isstring: ^4.0.1 - lodash.once: ^4.0.0 + jws: ^3.2.2 + lodash: ^4.17.21 ms: ^2.1.1 - checksum: b1225914e32bd44389872c9fbc1306eecad7e4aac031e33e39915b5ad687d92f10cd0dd0bcfc29cada26fb98fd04c6f39379b7d3634e173196cd1b239f663520 + semver: ^7.3.8 + checksum: b9181cecf9df99f1dc0253f91ba000a1aa4d91f5816d1608c0dba61a5623726a0bfe200b51df25de18c1a6000825d231ad7ce2788aa54fd48dcb760ad9eb9514 languageName: node linkType: hard @@ -15341,7 +15336,7 @@ __metadata: languageName: node linkType: hard -"jws@npm:^3.1.5": +"jws@npm:^3.2.2": version: 3.2.2 resolution: "jws@npm:3.2.2" dependencies: @@ -15762,34 +15757,6 @@ __metadata: languageName: node linkType: hard -"lodash.includes@npm:^4.3.0": - version: 4.3.0 - resolution: "lodash.includes@npm:4.3.0" - checksum: 71092c130515a67ab3bd928f57f6018434797c94def7f46aafa417771e455ce3a4834889f4267b17887d7f75297dfabd96231bf704fd2b8c5096dc4a913568b6 - languageName: node - linkType: hard - -"lodash.isboolean@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isboolean@npm:3.0.3" - checksum: b70068b4a8b8837912b54052557b21fc4774174e3512ed3c5b94621e5aff5eb6c68089d0a386b7e801d679cd105d2e35417978a5e99071750aa2ed90bffd0250 - languageName: node - linkType: hard - -"lodash.isinteger@npm:^4.0.4": - version: 4.0.4 - resolution: "lodash.isinteger@npm:4.0.4" - checksum: 6034821b3fc61a2ffc34e7d5644bb50c5fd8f1c0121c554c21ac271911ee0c0502274852845005f8651d51e199ee2e0cfebfe40aaa49c7fe617f603a8a0b1691 - languageName: node - linkType: hard - -"lodash.isnumber@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isnumber@npm:3.0.3" - checksum: 913784275b565346255e6ae6a6e30b760a0da70abc29f3e1f409081585875105138cda4a429ff02577e1bc0a7ae2a90e0a3079a37f3a04c3d6c5aaa532f4cab2 - languageName: node - linkType: hard - "lodash.isplainobject@npm:^4.0.6": version: 4.0.6 resolution: "lodash.isplainobject@npm:4.0.6" @@ -15797,13 +15764,6 @@ __metadata: languageName: node linkType: hard -"lodash.isstring@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.isstring@npm:4.0.1" - checksum: eaac87ae9636848af08021083d796e2eea3d02e80082ab8a9955309569cb3a463ce97fd281d7dc119e402b2e7d8c54a23914b15d2fc7fff56461511dc8937ba0 - languageName: node - linkType: hard - "lodash.memoize@npm:^4.1.2": version: 4.1.2 resolution: "lodash.memoize@npm:4.1.2" @@ -15818,7 +15778,7 @@ __metadata: languageName: node linkType: hard -"lodash.once@npm:^4.0.0, lodash.once@npm:^4.1.1": +"lodash.once@npm:^4.1.1": version: 4.1.1 resolution: "lodash.once@npm:4.1.1" checksum: d768fa9f9b4e1dc6453be99b753906f58990e0c45e7b2ca5a3b40a33111e5d17f6edf2f768786e2716af90a8e78f8f91431ab8435f761fef00f9b0c256f6d245 @@ -24945,9 +24905,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.75.0, webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5.9.0": - version: 5.75.0 - resolution: "webpack@npm:5.75.0" +"webpack@npm:5.76.1, webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5.9.0": + version: 5.76.1 + resolution: "webpack@npm:5.76.1" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^0.0.51 @@ -24978,7 +24938,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 2bcc5f3c195f375944e8af2f00bf2feea39cb9fda5f763b0d1b00077f1c51783db25c94d3fae96a07dead9fa085e6ae7474417e5ab31719c9776ea5969ceb83a + checksum: b01fe0bc2dbca0e10d290ddb0bf81e807a031de48028176e2b21afd696b4d3f25ab9accdad888ef4a1f7c7f4d41f13d5bf2395b7653fdf3e5e3dafa54e56dab2 languageName: node linkType: hard From 45ecca3222cf1a8672605f8b055dd80e300ca44d Mon Sep 17 00:00:00 2001 From: dobri1408 <50819975+dobri1408@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:25:59 +0200 Subject: [PATCH 280/326] (fix): Paste button disappearing while coping from nested blocks (#4505) Co-authored-by: Tiberiu Ichim --- news/4505.bugfix | 1 + src/components/manage/Form/BlocksToolbar.jsx | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 news/4505.bugfix diff --git a/news/4505.bugfix b/news/4505.bugfix new file mode 100644 index 0000000000..bcefcf8101 --- /dev/null +++ b/news/4505.bugfix @@ -0,0 +1 @@ +(fix): Paste button disappearing while coping from nested blocks @dobri1408 diff --git a/src/components/manage/Form/BlocksToolbar.jsx b/src/components/manage/Form/BlocksToolbar.jsx index 87c3554f68..49a315d8c6 100644 --- a/src/components/manage/Form/BlocksToolbar.jsx +++ b/src/components/manage/Form/BlocksToolbar.jsx @@ -177,7 +177,11 @@ export class BlocksToolbarComponent extends React.Component { '' )} {selectedBlock && (blocksClipboard?.cut || blocksClipboard?.copy) && ( - + + )} {this.state.linkIntegrityBreakages.length > 0 ? (

    From 2a4c0dce31c8e83bf3cd895bc4d61a53d5c765ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 15 Mar 2023 18:27:01 +0100 Subject: [PATCH 291/326] 4433 as a breaking change, add upgrade guide (#4540) --- docs/source/upgrade-guide/index.md | 5 +++++ news/4433.breaking | 1 + news/4433.feature | 1 - 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 news/4433.breaking delete mode 100644 news/4433.feature diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 30cb937c14..e31d31806a 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -59,6 +59,11 @@ This is better from the UI point of view, since any other element can take prece If you have customized the `BlockChooser` in any way could be that this now could interact with your customizations. +### Removed `hamburgers` library + +The `hamburgers` library was removed from core Volto, replaced by a much more lightweight approach. +If your theme or add-ons relied on it, add it again as a dependency in them, or adopt the CSS part that you are using in them. + (volto-upgrade-guide-16.x.x)= ## Upgrading to Volto 16.x.x diff --git a/news/4433.breaking b/news/4433.breaking new file mode 100644 index 0000000000..9fd7e71cb8 --- /dev/null +++ b/news/4433.breaking @@ -0,0 +1 @@ +Add custom CSS animation to hamburger menu. Removed `hamburgers` dependency. @danalvrz diff --git a/news/4433.feature b/news/4433.feature deleted file mode 100644 index c0047b763a..0000000000 --- a/news/4433.feature +++ /dev/null @@ -1 +0,0 @@ -Add custom CSS animation to hamburger menu, to avoid third party dependency. @danalvrz \ No newline at end of file From 889d25486971a866acad03408616a5c6313bdc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 15 Mar 2023 19:35:06 +0100 Subject: [PATCH 292/326] Add upgrade guide for 4504 (#4542) --- docs/source/upgrade-guide/index.md | 6 ++++++ news/4542.documentation | 1 + 2 files changed, 7 insertions(+) create mode 100644 news/4542.documentation diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index e31d31806a..02cace9ff7 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -64,6 +64,12 @@ If you have customized the `BlockChooser` in any way could be that this now coul The `hamburgers` library was removed from core Volto, replaced by a much more lightweight approach. If your theme or add-ons relied on it, add it again as a dependency in them, or adopt the CSS part that you are using in them. +### Fixed i18n script by taking into account the real add-on order + +By fixing this, we are potentially breaking how the locales were applied, since now the order will be the correct. +Please check the translations of your project and add-ons and verify that the translations are still correct. +This could be specially true if you did translation overrides or two add-ons were using different translations for the same `msgid` or there were conflicting `msgid` in different add-ons. + (volto-upgrade-guide-16.x.x)= ## Upgrading to Volto 16.x.x diff --git a/news/4542.documentation b/news/4542.documentation new file mode 100644 index 0000000000..7ee1bc739a --- /dev/null +++ b/news/4542.documentation @@ -0,0 +1 @@ +Add upgrade guide for 4504 @sneridagh From 65fd8a737d4f4c07097703b83d49142e65f597e2 Mon Sep 17 00:00:00 2001 From: Rob Gietema Date: Wed, 15 Mar 2023 19:37:40 +0100 Subject: [PATCH 293/326] Make Drag and Drop list work with container-type inline-size. (#4497) --- news/4497.bugfix | 1 + .../manage/DragDropList/DragDropList.jsx | 105 +++++++++++------- 2 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 news/4497.bugfix diff --git a/news/4497.bugfix b/news/4497.bugfix new file mode 100644 index 0000000000..521ca84511 --- /dev/null +++ b/news/4497.bugfix @@ -0,0 +1 @@ +Make Drag and Drop list work with container-type inline-size. @robgietema \ No newline at end of file diff --git a/src/components/manage/DragDropList/DragDropList.jsx b/src/components/manage/DragDropList/DragDropList.jsx index 0e78531429..c1e9d9ec03 100644 --- a/src/components/manage/DragDropList/DragDropList.jsx +++ b/src/components/manage/DragDropList/DragDropList.jsx @@ -3,7 +3,7 @@ import { isEmpty } from 'lodash'; import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; import { v4 as uuid } from 'uuid'; -const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex) => { +const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex, uid) => { // Because of the margin rendering rules, there is no easy // way to calculate the offset of the placeholder. // @@ -13,12 +13,16 @@ const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex) => { // // To get a placeholder that looks good in all cases, we // fill up the space between the previous and the next element. - const childrenArray = [...draggedDOM.parentNode.children]; + const queryAttr = 'data-rbd-droppable-id'; + const domQuery = `[${queryAttr}='${uid}']`; + const parentDOM = document.querySelector(domQuery); + + const childrenArray = [...parentDOM.children]; // Remove the source element childrenArray.splice(sourceIndex, 1); // Also remove the placeholder that the library always inserts at the end childrenArray.splice(-1, 1); - const parentRect = draggedDOM.parentNode.getBoundingClientRect(); + const parentRect = parentDOM.getBoundingClientRect(); const prevNode = childrenArray[destinationIndex - 1]; const nextNode = childrenArray[destinationIndex]; let top, bottom; @@ -40,9 +44,7 @@ const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex) => { return { clientY: top, clientHeight: bottom - top, - clientX: parseFloat( - window.getComputedStyle(draggedDOM.parentNode).paddingLeft, - ), + clientX: parseFloat(window.getComputedStyle(parentDOM).paddingLeft), clientWidth: draggedDOM.clientWidth, }; }; @@ -63,17 +65,22 @@ const DragDropList = (props) => { // queueing timed action const timer = useRef(null); - const onDragStart = React.useCallback((event) => { - clearTimeout(timer.current); - const queryAttr = 'data-rbd-draggable-id'; - const domQuery = `[${queryAttr}='${event.draggableId}']`; - const draggedDOM = document.querySelector(domQuery); - if (!draggedDOM) { - return; - } - const sourceIndex = event.source.index; - setPlaceholderProps(getPlaceholder(draggedDOM, sourceIndex, sourceIndex)); - }, []); + const onDragStart = React.useCallback( + (event) => { + clearTimeout(timer.current); + const queryAttr = 'data-rbd-draggable-id'; + const domQuery = `[${queryAttr}='${event.draggableId}']`; + const draggedDOM = document.querySelector(domQuery); + if (!draggedDOM) { + return; + } + const sourceIndex = event.source.index; + setPlaceholderProps( + getPlaceholder(draggedDOM, sourceIndex, sourceIndex, uid), + ); + }, + [uid], + ); const onDragEnd = React.useCallback( (result) => { @@ -84,30 +91,33 @@ const DragDropList = (props) => { [onMoveItem], ); - const onDragUpdate = React.useCallback((update) => { - clearTimeout(timer.current); - setPlaceholderProps({}); - if (!update.destination) { - return; - } - const draggableId = update.draggableId; - const queryAttr = 'data-rbd-draggable-id'; - const domQuery = `[${queryAttr}='${draggableId}']`; - const draggedDOM = document.querySelector(domQuery); - if (!draggedDOM) { - return; - } - const sourceIndex = update.source.index; - const destinationIndex = update.destination.index; - // Wait until the animations have finished, to make it look good. - timer.current = setTimeout( - () => - setPlaceholderProps( - getPlaceholder(draggedDOM, sourceIndex, destinationIndex), - ), - 250, - ); - }, []); + const onDragUpdate = React.useCallback( + (update) => { + clearTimeout(timer.current); + setPlaceholderProps({}); + if (!update.destination) { + return; + } + const draggableId = update.draggableId; + const queryAttr = 'data-rbd-draggable-id'; + const domQuery = `[${queryAttr}='${draggableId}']`; + const draggedDOM = document.querySelector(domQuery); + if (!draggedDOM) { + return; + } + const sourceIndex = update.source.index; + const destinationIndex = update.destination.index; + // Wait until the animations have finished, to make it look good. + timer.current = setTimeout( + () => + setPlaceholderProps( + getPlaceholder(draggedDOM, sourceIndex, destinationIndex, uid), + ), + 250, + ); + }, + [uid], + ); const AsDomComponent = as; return ( @@ -116,7 +126,18 @@ const DragDropList = (props) => { onDragUpdate={onDragUpdate} onDragEnd={onDragEnd} > - + { + const index = rubric.source.index; + return children({ + child: childList[index][1], + childId: childList[index][0], + index, + draginfo: provided, + }); + }} + > {(provided, snapshot) => ( Date: Wed, 15 Mar 2023 19:51:11 +0100 Subject: [PATCH 294/326] Update 16.16.0 and 16.17.0 releases changelog (#4544) --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02e9f73734..42c95bf61c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,35 @@ - Fix English and MyST grammar and syntax from PR #4285 @stevepiercy [#4331](https://github.com/plone/volto/issues/4331) - Use a universal static path for both documentation and volto repos. @stevepiercy [#4376](https://github.com/plone/volto/issues/4376) +## 16.17.0 (2023-03-15) + +### Feature + +- Add option to hide empty listing blocks @ksuess [#4393](https://github.com/plone/volto/issues/4393) + +### Bugfix + +- Added block prop to BlockDataForm in the Edit component of ToC. If block is not passed, OnChangeBlock will be called with undefined block id. @tedw87 [#4110](https://github.com/plone/volto/issues/4110) +- Fix focus steal in Form @tedw87 [#4230](https://github.com/plone/volto/issues/4230) +- Fixed paste issue in Table Block and added cypress test for pasting text in Table Block. [#4301](https://github.com/plone/volto/issues/4301) +- Fixed i18n script to avoid overwriting translations with an empty msgstr @danalvrz [#4316](https://github.com/plone/volto/issues/4316) +- bugfix: conditionally render all delete items in confirm widget [#4336](https://github.com/plone/volto/issues/4336) +- Make the Site Setup control panel responsive for small screen devices. @lord2anil [#4484](https://github.com/plone/volto/issues/4484) +- The menu for the contents page was unresponsive on mobile devices. Fixed this by changing the menu overflow to scroll. @sudhanshu1309 [#4492](https://github.com/plone/volto/issues/4492) +- (fix): Paste button disappearing while coping from nested blocks @dobri1408 [#4505](https://github.com/plone/volto/issues/4505) +- Fix flaky Cypress test introduced in #4521 @sneridagh [#4522](https://github.com/plone/volto/issues/4522) + +### Documentation + +- Fix training urls @ksuess [#4502](https://github.com/plone/volto/issues/4502) + + +## 16.16.0 (2023-03-09) + +### Feature + +- Add directive to cache stable resources in browser or intermediate server for 365 days by default directly in the SSR Express server, static resource that could change after a new deployment for 1 minute. @mamico [#2216](https://github.com/plone/volto/issues/2216) + ## 16.15.0 (2023-03-08) From 9687a74e81c3f9740fcb944cb3f726c1ba391caa Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Wed, 15 Mar 2023 20:08:22 +0100 Subject: [PATCH 295/326] Release 17.0.0-alpha.2 --- CHANGELOG.md | 32 +++++++++++++++++++++++++++++++ news/3997.bugfix | 1 - news/4110.bugfix | 1 - news/4230.bugfix | 1 - news/4301.bugfix | 1 - news/4316.bugfix | 1 - news/4336.bugfix | 1 - news/4393.feature | 1 - news/4433.breaking | 1 - news/4484.bugfix | 2 -- news/4492.bugfix | 1 - news/4495.breaking | 1 - news/4497.bugfix | 1 - news/4502.documentation | 1 - news/4505.bugfix | 1 - news/4520.bugfix | 1 - news/4522.bugfix | 1 - news/4542.documentation | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 20 files changed, 34 insertions(+), 20 deletions(-) delete mode 100644 news/3997.bugfix delete mode 100644 news/4110.bugfix delete mode 100644 news/4230.bugfix delete mode 100644 news/4301.bugfix delete mode 100644 news/4316.bugfix delete mode 100644 news/4336.bugfix delete mode 100644 news/4393.feature delete mode 100644 news/4433.breaking delete mode 100644 news/4484.bugfix delete mode 100644 news/4492.bugfix delete mode 100644 news/4495.breaking delete mode 100644 news/4497.bugfix delete mode 100644 news/4502.documentation delete mode 100644 news/4505.bugfix delete mode 100644 news/4520.bugfix delete mode 100644 news/4522.bugfix delete mode 100644 news/4542.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 42c95bf61c..3a4a695541 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,38 @@ +## 17.0.0-alpha.2 (2023-03-15) + +### Breaking + +- Add custom CSS animation to hamburger menu. Removed `hamburgers` dependency. @danalvrz [#4433](https://github.com/plone/volto/issues/4433) +- Improve i18n script ordering of addons, so that addons can override translations from their dependencies. @davisagli [#4495](https://github.com/plone/volto/issues/4495) + +### Feature + +- Add option to hide empty listing blocks @ksuess [#4393](https://github.com/plone/volto/issues/4393) + +### Bugfix + +- Update build dependencies (razzle and react-dev-utils) @davisagli [#3997](https://github.com/plone/volto/issues/3997) +- Added block prop to BlockDataForm in the Edit component of ToC. If block is not passed, OnChangeBlock will be called with undefined block id. @tedw87 [#4110](https://github.com/plone/volto/issues/4110) +- Fix focus steal in Form @tedw87 [#4230](https://github.com/plone/volto/issues/4230) +- Fixed paste issue in Table Block and added cypress test for pasting text in Table Block. [#4301](https://github.com/plone/volto/issues/4301) +- Fixed i18n script to avoid overwriting translations with an empty msgstr @danalvrz [#4316](https://github.com/plone/volto/issues/4316) +- bugfix: conditionally render all delete items in confirm widget [#4336](https://github.com/plone/volto/issues/4336) +- Make the Site Setup control panel responsive for small screen devices. @lord2anil [#4484](https://github.com/plone/volto/issues/4484) +- The menu for the contents page was unresponsive on mobile devices. Fixed this by changing the menu overflow to scroll. @sudhanshu1309 [#4492](https://github.com/plone/volto/issues/4492) +- Make Drag and Drop list work with container-type inline-size. @robgietema [#4497](https://github.com/plone/volto/issues/4497) +- (fix): Paste button disappearing while coping from nested blocks @dobri1408 [#4505](https://github.com/plone/volto/issues/4505) +- Patch updates for some dependencies. @davisagli [#4520](https://github.com/plone/volto/issues/4520) +- Fix flaky Cypress test introduced in #4521 @sneridagh [#4522](https://github.com/plone/volto/issues/4522) + +### Documentation + +- Fix training urls @ksuess [#4502](https://github.com/plone/volto/issues/4502) +- Add upgrade guide for 4504 @sneridagh [#4542](https://github.com/plone/volto/issues/4542) + + ## 17.0.0-alpha.1 (2023-03-09) ### Feature diff --git a/news/3997.bugfix b/news/3997.bugfix deleted file mode 100644 index 78fd386d0a..0000000000 --- a/news/3997.bugfix +++ /dev/null @@ -1 +0,0 @@ -Update build dependencies (razzle and react-dev-utils) @davisagli diff --git a/news/4110.bugfix b/news/4110.bugfix deleted file mode 100644 index 6d0a130901..0000000000 --- a/news/4110.bugfix +++ /dev/null @@ -1 +0,0 @@ -Added block prop to BlockDataForm in the Edit component of ToC. If block is not passed, OnChangeBlock will be called with undefined block id. @tedw87 \ No newline at end of file diff --git a/news/4230.bugfix b/news/4230.bugfix deleted file mode 100644 index a685d65a5d..0000000000 --- a/news/4230.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix focus steal in Form @tedw87 \ No newline at end of file diff --git a/news/4301.bugfix b/news/4301.bugfix deleted file mode 100644 index dacf3365a1..0000000000 --- a/news/4301.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed paste issue in Table Block and added cypress test for pasting text in Table Block. \ No newline at end of file diff --git a/news/4316.bugfix b/news/4316.bugfix deleted file mode 100644 index 4df1b112a6..0000000000 --- a/news/4316.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed i18n script to avoid overwriting translations with an empty msgstr @danalvrz \ No newline at end of file diff --git a/news/4336.bugfix b/news/4336.bugfix deleted file mode 100644 index e4fac0c50a..0000000000 --- a/news/4336.bugfix +++ /dev/null @@ -1 +0,0 @@ -bugfix: conditionally render all delete items in confirm widget \ No newline at end of file diff --git a/news/4393.feature b/news/4393.feature deleted file mode 100644 index 977e5aec23..0000000000 --- a/news/4393.feature +++ /dev/null @@ -1 +0,0 @@ -Add option to hide empty listing blocks @ksuess \ No newline at end of file diff --git a/news/4433.breaking b/news/4433.breaking deleted file mode 100644 index 9fd7e71cb8..0000000000 --- a/news/4433.breaking +++ /dev/null @@ -1 +0,0 @@ -Add custom CSS animation to hamburger menu. Removed `hamburgers` dependency. @danalvrz diff --git a/news/4484.bugfix b/news/4484.bugfix deleted file mode 100644 index aabce324d5..0000000000 --- a/news/4484.bugfix +++ /dev/null @@ -1,2 +0,0 @@ -Make the Site Setup control panel responsive for small screen devices. @lord2anil - diff --git a/news/4492.bugfix b/news/4492.bugfix deleted file mode 100644 index a510c9c955..0000000000 --- a/news/4492.bugfix +++ /dev/null @@ -1 +0,0 @@ -The menu for the contents page was unresponsive on mobile devices. Fixed this by changing the menu overflow to scroll. @sudhanshu1309 diff --git a/news/4495.breaking b/news/4495.breaking deleted file mode 100644 index 5aacb9fc94..0000000000 --- a/news/4495.breaking +++ /dev/null @@ -1 +0,0 @@ -Improve i18n script ordering of addons, so that addons can override translations from their dependencies. @davisagli diff --git a/news/4497.bugfix b/news/4497.bugfix deleted file mode 100644 index 521ca84511..0000000000 --- a/news/4497.bugfix +++ /dev/null @@ -1 +0,0 @@ -Make Drag and Drop list work with container-type inline-size. @robgietema \ No newline at end of file diff --git a/news/4502.documentation b/news/4502.documentation deleted file mode 100644 index cc20ee1c42..0000000000 --- a/news/4502.documentation +++ /dev/null @@ -1 +0,0 @@ -Fix training urls @ksuess \ No newline at end of file diff --git a/news/4505.bugfix b/news/4505.bugfix deleted file mode 100644 index bcefcf8101..0000000000 --- a/news/4505.bugfix +++ /dev/null @@ -1 +0,0 @@ -(fix): Paste button disappearing while coping from nested blocks @dobri1408 diff --git a/news/4520.bugfix b/news/4520.bugfix deleted file mode 100644 index 19630ab2e3..0000000000 --- a/news/4520.bugfix +++ /dev/null @@ -1 +0,0 @@ -Patch updates for some dependencies. @davisagli diff --git a/news/4522.bugfix b/news/4522.bugfix deleted file mode 100644 index 3fc94a0525..0000000000 --- a/news/4522.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix flaky Cypress test introduced in #4521 @sneridagh diff --git a/news/4542.documentation b/news/4542.documentation deleted file mode 100644 index 7ee1bc739a..0000000000 --- a/news/4542.documentation +++ /dev/null @@ -1 +0,0 @@ -Add upgrade guide for 4504 @sneridagh diff --git a/package.json b/package.json index 98ea10074e..3fb6c324a1 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.1", + "version": "17.0.0-alpha.2", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json index e4ad38ef6e..d6a6dc6f68 100644 --- a/packages/volto-slate/package.json +++ b/packages/volto-slate/package.json @@ -1,6 +1,6 @@ { "name": "@plone/volto-slate", - "version": "17.0.0-alpha.1", + "version": "17.0.0-alpha.2", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From 3797966d7c868b46121bc90d5baf2fa893515046 Mon Sep 17 00:00:00 2001 From: "Mohd. Yahya" <59670962+yahya-cloud@users.noreply.github.com> Date: Thu, 16 Mar 2023 01:02:20 +0530 Subject: [PATCH 296/326] url update and duplicate import deletion (#4523) Co-authored-by: Steve Piercy --- docs/source/blocks/settings.md | 1 - news/4523.documentation | 1 + packages/generator-volto/generators/app/templates/README.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 news/4523.documentation diff --git a/docs/source/blocks/settings.md b/docs/source/blocks/settings.md index e9d81009c5..e92e736042 100644 --- a/docs/source/blocks/settings.md +++ b/docs/source/blocks/settings.md @@ -26,7 +26,6 @@ import CardTeaserView from '@package/components/Blocks/CardTeaserView'; import DefaultColumnRenderer from '@package/components/Blocks/DefaultColumnRenderer'; import NumberColumnRenderer from '@package/components/Blocks/NumberColumnRenderer'; import ColoredColumnRenderer from '@package/components/Blocks/ColoredColumnRenderer'; -import CardTeaserView from '@package/components/Blocks/CardTeaserView'; import CustomSchemaEnhancer from '@package/components/Blocks/CustomSchemaEnhancer'; diff --git a/news/4523.documentation b/news/4523.documentation new file mode 100644 index 0000000000..9236a1fd29 --- /dev/null +++ b/news/4523.documentation @@ -0,0 +1 @@ +Deleted duplicate import and fixed training URLs. @yahya-cloud \ No newline at end of file diff --git a/packages/generator-volto/generators/app/templates/README.md b/packages/generator-volto/generators/app/templates/README.md index 1410a4d24e..9547b698b2 100644 --- a/packages/generator-volto/generators/app/templates/README.md +++ b/packages/generator-volto/generators/app/templates/README.md @@ -1,6 +1,6 @@ ## Documentation -A training on how to create your own website using Volto is available as part of the Plone training at [https://training.plone.org/5/volto/index.html](https://training.plone.org/5/volto/index.html). +[Volto Hands-On](https://training.plone.org/voltohandson/index.html) is a training on how to create your own website. ## Quick Start From 15b83f26e21b87acaf058fca0ac6016aa3b06f22 Mon Sep 17 00:00:00 2001 From: iRohitSingh <61353484+iRohitSingh@users.noreply.github.com> Date: Thu, 16 Mar 2023 02:48:44 +0530 Subject: [PATCH 297/326] Fix Search is case sensitive in Block chooser (#4527) Co-authored-by: David Glick --- news/4526.bugfix | 1 + src/components/manage/BlockChooser/BlockChooser.jsx | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 news/4526.bugfix diff --git a/news/4526.bugfix b/news/4526.bugfix new file mode 100644 index 0000000000..a4f5ed0bdb --- /dev/null +++ b/news/4526.bugfix @@ -0,0 +1 @@ +Fix Search is case sensitive in Block chooser @iRohitSingh \ No newline at end of file diff --git a/src/components/manage/BlockChooser/BlockChooser.jsx b/src/components/manage/BlockChooser/BlockChooser.jsx index 5c97683981..921d6b79a6 100644 --- a/src/components/manage/BlockChooser/BlockChooser.jsx +++ b/src/components/manage/BlockChooser/BlockChooser.jsx @@ -89,14 +89,18 @@ const BlockChooser = ({ function blocksAvailableFilter(blocks) { return blocks.filter( (block) => - getFormatMessage(block.title).toLowerCase().includes(filterValue) || + getFormatMessage(block.title) + .toLowerCase() + .includes(filterValue.toLowerCase()) || filterVariations(block)?.length, ); } function filterVariations(block) { return block.variations?.filter( (variation) => - getFormatMessage(variation.title).toLowerCase().includes(filterValue) && + getFormatMessage(variation.title) + .toLowerCase() + .includes(filterValue.toLowerCase()) && !variation.title.toLowerCase().includes('default'), ); } From ed7cb97d5ccead4bcaf175837196494067aa28d3 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Thu, 16 Mar 2023 03:48:08 -0700 Subject: [PATCH 298/326] Fix grammar in PR #4542 (#4555) --- docs/source/upgrade-guide/index.md | 6 +++--- news/4555.documentation | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 news/4555.documentation diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 02cace9ff7..166f10263c 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -66,9 +66,9 @@ If your theme or add-ons relied on it, add it again as a dependency in them, or ### Fixed i18n script by taking into account the real add-on order -By fixing this, we are potentially breaking how the locales were applied, since now the order will be the correct. -Please check the translations of your project and add-ons and verify that the translations are still correct. -This could be specially true if you did translation overrides or two add-ons were using different translations for the same `msgid` or there were conflicting `msgid` in different add-ons. +By fixing this, we may break how the locales were applied, since the order will now be correct. +Please check the translations of your project and add-ons, and verify that the translations are still correct. +This could be especially true if you did translation overrides, two add-ons were using different translations for the same `msgid`, or there were conflicting `msgid`s in different add-ons. (volto-upgrade-guide-16.x.x)= diff --git a/news/4555.documentation b/news/4555.documentation new file mode 100644 index 0000000000..75b7876611 --- /dev/null +++ b/news/4555.documentation @@ -0,0 +1 @@ +Fix grammar in PR #4542. @stevepiercy From 48d7d62e8c5f122f6b6cae2860650e3547187ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Thu, 16 Mar 2023 11:55:12 +0100 Subject: [PATCH 299/326] Changelog for 16.17.1 (#4556) --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a4a695541..e9fe55acd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,18 @@ - Fix English and MyST grammar and syntax from PR #4285 @stevepiercy [#4331](https://github.com/plone/volto/issues/4331) - Use a universal static path for both documentation and volto repos. @stevepiercy [#4376](https://github.com/plone/volto/issues/4376) + +## 16.17.1 (2023-03-16) + + ### Bugfix + + - Fix Search is case sensitive in Block chooser @iRohitSingh [#4526](https://github.com/plone/volto/issues/4526) + + ### Documentation + + - Deleted duplicate import and fixed training URLs. @yahya-cloud [#4523](https://github.com/plone/volto/issues/4523) + + ## 16.17.0 (2023-03-15) ### Feature From 0a0f319c2c75ca8ebed8fcf76e236087d5f4e86e Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Fri, 17 Mar 2023 22:42:48 -0700 Subject: [PATCH 300/326] Fix broken links at ReactJS (#4569) --- CHANGELOG.md | 4 ++-- docs/source/getting-started/roadmap.md | 2 +- news/4569.documentation | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 news/4569.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fe55acd8..81db5d5a22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2447,7 +2447,7 @@ See https://6.docs.plone.org/volto/upgrade-guide/index.html for more information - Use Plone logo @ericof - Update favicon and related tags with best practices @sneridagh - Enable to be able to use the internal proxy in production as well @sneridagh -- Add runtime configuration for `@babel/plugin-transform-react-jsx` set to `automatic`. This enables the new JSX runtime: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html So no longer `import React from 'react'` is needed anymore. @sneridagh +- Add runtime configuration for `@babel/plugin-transform-react-jsx` set to `automatic`. This enables the new JSX runtime: https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html So no longer `import React from 'react'` is needed anymore. @sneridagh - Add `autocomplete` Widget component - It holds off the vocabulary endpoint pull until you search (more than 2 chars). Useful when dealing with huge vocabularies @sneridagh @reebalazs - Add new listing block option "fullobjects" per variation @ksuess - `FormFieldWrapper` accepts now strings and elements for description @nzambello @@ -2577,7 +2577,7 @@ See https://6.docs.plone.org/volto/upgrade-guide/index.html for more information ### Feature -- Add runtime configuration for `@babel/plugin-transform-react-jsx` set to `automatic`. This enables the new JSX runtime: https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html So no longer `import React from 'react'` is needed anymore. +- Add runtime configuration for `@babel/plugin-transform-react-jsx` set to `automatic`. This enables the new JSX runtime: https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html So no longer `import React from 'react'` is needed anymore. - Update favicon and related tags with best practices @sneridagh ### Bugfix diff --git a/docs/source/getting-started/roadmap.md b/docs/source/getting-started/roadmap.md index 933ef0515f..3af14a07b0 100644 --- a/docs/source/getting-started/roadmap.md +++ b/docs/source/getting-started/roadmap.md @@ -28,7 +28,7 @@ As is the case with similar modern Javascript-based applications, you should kno - Modern Javascript development. Volto uses next-generation Javascript. Follow the [ES6 guide](https://flaviocopes.com/es6/) to get up to speed. - React knowledge - [basic level is - fine](https://reactjs.org/tutorial/tutorial.html) for the beginning, you'll progress + fine](https://react.dev/learn/tutorial-tic-tac-toe) for the beginning, you'll progress along the way. React itself is a simple and well documented framework. - A basic understanding of Javascript [CommonJS](https://flaviocopes.com/commonjs/), diff --git a/news/4569.documentation b/news/4569.documentation new file mode 100644 index 0000000000..a08842d90d --- /dev/null +++ b/news/4569.documentation @@ -0,0 +1 @@ +Fix broken links at ReactJS.org. @stevepiercy From d3865b3ac9e5c1213058373e6ecf5422060d7ab0 Mon Sep 17 00:00:00 2001 From: Mohammad Hussain <99530996+MAX-786@users.noreply.github.com> Date: Sun, 19 Mar 2023 02:55:53 +0530 Subject: [PATCH 301/326] Using Vale in CI for spellcheck (#4423) Co-authored-by: Steve Piercy --- .github/workflows/docs.yml | 10 ++++++++++ .gitignore | 1 + .vale.ini | 10 ++++++++++ news/4423.feature | 1 + styles/Vocab/Base/accept.txt | 0 styles/Vocab/Base/reject.txt | 0 styles/Vocab/Plone/accept.txt | 8 ++++++++ styles/Vocab/Plone/reject.txt | 0 8 files changed, 30 insertions(+) create mode 100644 .vale.ini create mode 100644 news/4423.feature create mode 100644 styles/Vocab/Base/accept.txt create mode 100644 styles/Vocab/Base/reject.txt create mode 100644 styles/Vocab/Plone/accept.txt create mode 100644 styles/Vocab/Plone/reject.txt diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bf6a564301..d9aacb121b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -37,3 +37,13 @@ jobs: - name: Build HTML documentation run: make docs-html + + - uses: errata-ai/vale-action@reviewdog + with: + # debug: true + files: all + env: + # Required, set by GitHub actions automatically: + # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + diff --git a/.gitignore b/.gitignore index 971b10aee2..ebcc210125 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ selenium-screenshot-*.png /selenium/ cypress/videos/ cypress/screenshots +/styles/Microsoft # Local environment setup .env diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 0000000000..da6af903f2 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,10 @@ +StylesPath = styles + +MinAlertLevel = suggestion + +Vocab = Base,Plone + +Packages = Microsoft + +[*.md] +BasedOnStyles = Vale, Microsoft diff --git a/news/4423.feature b/news/4423.feature new file mode 100644 index 0000000000..8901b62863 --- /dev/null +++ b/news/4423.feature @@ -0,0 +1 @@ +Add Vale to CI for spell and style checks. @MAX-786 diff --git a/styles/Vocab/Base/accept.txt b/styles/Vocab/Base/accept.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/styles/Vocab/Base/reject.txt b/styles/Vocab/Base/reject.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/styles/Vocab/Plone/accept.txt b/styles/Vocab/Plone/accept.txt new file mode 100644 index 0000000000..78bcf7537b --- /dev/null +++ b/styles/Vocab/Plone/accept.txt @@ -0,0 +1,8 @@ +`plone.restapi` +`plone.volto` +npm +Plone +Razzle +RichText +Volto +Zope diff --git a/styles/Vocab/Plone/reject.txt b/styles/Vocab/Plone/reject.txt new file mode 100644 index 0000000000..e69de29bb2 From 0da6bc8a212f1bcff2d659e071f5c3dcde3a3891 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Tue, 21 Mar 2023 00:32:13 -0700 Subject: [PATCH 302/326] Fix video warnings link errors (#4578) --- docs/source/_static/custom.css | 4 ++++ docs/source/getting-started/install.md | 2 +- docs/source/user-manual/copy-paste-blocks.md | 2 -- news/4578.documentation | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 news/4578.documentation diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css index aa980421c2..d792d49f36 100644 --- a/docs/source/_static/custom.css +++ b/docs/source/_static/custom.css @@ -240,3 +240,7 @@ span.guilabel, span.menuselection { font-style: italic; white-space: nowrap; } + +video { + width: 100%; +} diff --git a/docs/source/getting-started/install.md b/docs/source/getting-started/install.md index 1e8dfc2460..7dd17ff64b 100644 --- a/docs/source/getting-started/install.md +++ b/docs/source/getting-started/install.md @@ -23,7 +23,7 @@ This chapter contains some legacy information that may be useful to Plone 5.2 de Volto can be installed in any operating system assuming that the following pre-requisites are met: -- [Node.js LTS (18.x)](https://nodejs.org/en/) +- [Node.js LTS (18.x)](https://nodejs.org/en) - [Python](https://www.python.org/) - See below for specific versions. - [Docker](https://www.docker.com/get-started) (if using the Plone docker images) diff --git a/docs/source/user-manual/copy-paste-blocks.md b/docs/source/user-manual/copy-paste-blocks.md index 1f922e2f75..c62544700e 100644 --- a/docs/source/user-manual/copy-paste-blocks.md +++ b/docs/source/user-manual/copy-paste-blocks.md @@ -27,7 +27,6 @@ This feature can be used by selecting a start block and an end block while holdi This will select all the blocks between the start and end blocks, allowing you to copy, cut, or delete multiple blocks at once. ```{video} /_static/user-manual/blocks/block-copy-cut.mp4 - :width: 100% ``` @@ -42,5 +41,4 @@ You can click the paste option Paste icon Date: Tue, 21 Mar 2023 10:48:01 +0100 Subject: [PATCH 303/326] Update message Add-on control panel (#4574) --- locales/ca/LC_MESSAGES/volto.po | 2 +- locales/de/LC_MESSAGES/volto.po | 2 +- locales/en/LC_MESSAGES/volto.po | 2 +- locales/es/LC_MESSAGES/volto.po | 2 +- locales/eu/LC_MESSAGES/volto.po | 2 +- locales/fr/LC_MESSAGES/volto.po | 2 +- locales/it/LC_MESSAGES/volto.po | 2 +- locales/ja/LC_MESSAGES/volto.po | 2 +- locales/nl/LC_MESSAGES/volto.po | 2 +- locales/pt/LC_MESSAGES/volto.po | 2 +- locales/pt_BR/LC_MESSAGES/volto.po | 2 +- locales/ro/LC_MESSAGES/volto.po | 2 +- locales/volto.pot | 2 +- locales/zh_CN/LC_MESSAGES/volto.po | 2 +- news/4574.bugfix | 1 + src/components/manage/Controlpanels/AddonsControlpanel.jsx | 6 +++--- .../__snapshots__/AddonsControlpanel.test.jsx.snap | 4 ++-- 17 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 news/4574.bugfix diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index dd9c9d8f9e..1133771312 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -93,7 +93,7 @@ msgid "Add (object list)" msgstr "" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Afegeix complements" diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index f3af5c2c05..8a717bc3d2 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -90,7 +90,7 @@ msgid "Add (object list)" msgstr "Hinzufügen" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Add-on hinzufügen" diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index d305b3ba72..30e4e39d71 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -84,7 +84,7 @@ msgid "Add (object list)" msgstr "" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index 45f795f134..5eaf110016 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -95,7 +95,7 @@ msgid "Add (object list)" msgstr "Añadir" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Añadir complementos" diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index 81f78b90f8..4f058ed3ab 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -91,7 +91,7 @@ msgid "Add (object list)" msgstr "Gehitu" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Gehitu gehigarriak" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 9870322dc1..5f9097b014 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -101,7 +101,7 @@ msgid "Add (object list)" msgstr "Ajouter (liste d'objets)" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Ajouter des modules" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index c7d64a0ead..ee1c27901c 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -84,7 +84,7 @@ msgid "Add (object list)" msgstr "Aggiungi" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Aggiungi Add-ons" diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index b59f2e75bd..53ba075dea 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -92,7 +92,7 @@ msgid "Add (object list)" msgstr "" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "以下のリストに新しいアドオンが表示されるようにするには、それをビルドアウト設定(buildout.cfg)に追加し、ビルドアウトコマンド(bin/buildout)を実行した後に、サーバプロセスを再起動します。" diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index c5ea31fc2b..17eac461c8 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -91,7 +91,7 @@ msgid "Add (object list)" msgstr "Toevoegen (object lijst)" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Voeg modules toe" diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index 04e893f019..40dae41756 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -92,7 +92,7 @@ msgid "Add (object list)" msgstr "" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "" diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index 516c58d2e6..d4d8f10d1e 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -94,7 +94,7 @@ msgid "Add (object list)" msgstr "Adicionar" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Adicionar complementos" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index eabec0fec6..88cc55fcdb 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -84,7 +84,7 @@ msgid "Add (object list)" msgstr "Adăugare (listă de obiecte)" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "Adăugare add-on-uri" diff --git a/locales/volto.pot b/locales/volto.pot index 2798937487..ad763942a7 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -86,7 +86,7 @@ msgid "Add (object list)" msgstr "" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "" diff --git a/locales/zh_CN/LC_MESSAGES/volto.po b/locales/zh_CN/LC_MESSAGES/volto.po index c463b10175..ac6774f8e8 100644 --- a/locales/zh_CN/LC_MESSAGES/volto.po +++ b/locales/zh_CN/LC_MESSAGES/volto.po @@ -90,7 +90,7 @@ msgid "Add (object list)" msgstr "添加(对象列表)" #: components/manage/Controlpanels/AddonsControlpanel -# defaultMessage: To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see +# defaultMessage: To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see msgid "Add Addons" msgstr "添加附件" diff --git a/news/4574.bugfix b/news/4574.bugfix new file mode 100644 index 0000000000..e5013ab087 --- /dev/null +++ b/news/4574.bugfix @@ -0,0 +1 @@ +Update message add-on control panel: remove 'buildout', update reference. @ksuess \ No newline at end of file diff --git a/src/components/manage/Controlpanels/AddonsControlpanel.jsx b/src/components/manage/Controlpanels/AddonsControlpanel.jsx index a1f8a8b384..1248c90cbf 100644 --- a/src/components/manage/Controlpanels/AddonsControlpanel.jsx +++ b/src/components/manage/Controlpanels/AddonsControlpanel.jsx @@ -43,7 +43,7 @@ const messages = defineMessages({ addAddons: { id: 'Add Addons', defaultMessage: - 'To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see', + 'To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see', }, addonsSettings: { id: 'Add-ons Settings', @@ -380,11 +380,11 @@ class AddonsControlpanel extends Component {   diff --git a/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap b/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap index 220fa5bff8..78ea65bb56 100644 --- a/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap +++ b/src/components/manage/Controlpanels/__snapshots__/AddonsControlpanel.test.jsx.snap @@ -31,10 +31,10 @@ exports[`AddonsControlpanel renders an addon control component 1`] = ` > Activate and deactivate add-ons in the lists below.

    - To make new add-ons show up here, add them to your buildout configuration, run buildout, and restart the server process. For detailed instructions see + To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see   From a44cfa2d28bc6c56bbf8d1ee8898ccc57b9ae77f Mon Sep 17 00:00:00 2001 From: Alok Kumar Date: Tue, 21 Mar 2023 22:44:13 +0530 Subject: [PATCH 304/326] =?UTF-8?q?InternalURl=20helper=20method=20should?= =?UTF-8?q?=20incorporate=20externalRoutes=20settings=20=E2=80=A6=20(#4560?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alin Voinea --- news/4559.bugfix | 1 + src/helpers/Url/Url.js | 22 +++++++++++++++++++--- src/helpers/Url/Url.test.js | 12 ++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 news/4559.bugfix diff --git a/news/4559.bugfix b/news/4559.bugfix new file mode 100644 index 0000000000..395e77aca0 --- /dev/null +++ b/news/4559.bugfix @@ -0,0 +1 @@ +InternalURl helper method should incorporate externalRoutes settings into consideration. @iFlameing \ No newline at end of file diff --git a/src/helpers/Url/Url.js b/src/helpers/Url/Url.js index c6bf7f2391..3038e4602d 100644 --- a/src/helpers/Url/Url.js +++ b/src/helpers/Url/Url.js @@ -7,6 +7,7 @@ import { last, memoize } from 'lodash'; import { urlRegex, telRegex, mailRegex } from './urlRegex'; import prependHttp from 'prepend-http'; import config from '@plone/volto/registry'; +import { matchPath } from 'react-router'; /** * Get base url. @@ -213,7 +214,17 @@ export function expandToBackendURL(path) { */ export function isInternalURL(url) { const { settings } = config; - return ( + + const isMatch = (config.settings.externalRoutes ?? []).find((route) => { + if (typeof route === 'object') { + return matchPath(flattenToAppURL(url), route.match); + } + return matchPath(flattenToAppURL(url), route); + }); + + const isExcluded = isMatch && Object.keys(isMatch)?.length > 0; + + const internalURL = url && (url.indexOf(settings.publicURL) !== -1 || (settings.internalApiPath && @@ -221,8 +232,13 @@ export function isInternalURL(url) { url.indexOf(settings.apiPath) !== -1 || url.charAt(0) === '/' || url.charAt(0) === '.' || - url.startsWith('#')) - ); + url.startsWith('#')); + + if (internalURL && isExcluded) { + return false; + } + + return internalURL; } /** diff --git a/src/helpers/Url/Url.test.js b/src/helpers/Url/Url.test.js index 597c307935..0c06f89d08 100644 --- a/src/helpers/Url/Url.test.js +++ b/src/helpers/Url/Url.test.js @@ -191,6 +191,7 @@ describe('Url', () => { expect(isInternalURL(href)).toBe(false); settings.internalApiPath = saved; }); + it('tells if an URL is internal if it is an anchor', () => { const href = '#anchor'; expect(isInternalURL(href)).toBe(true); @@ -211,6 +212,17 @@ describe('Url', () => { const href = undefined; expect(isInternalURL(href)).toBe(undefined); }); + it('tells if an URL is external if settings.externalroutes is persent.', () => { + const url = `https://localhost:3000/fb/my-page/contents`; + const blacklistedurl = '/blacklisted'; + settings.externalRoutes = [ + { title: 'My Page', match: '/fb' }, + '/blacklisted', + ]; + settings.publicURL = 'https://localhost:3000'; + expect(isInternalURL(url)).toBe(false); + expect(isInternalURL(blacklistedurl)).toBe(false); + }); }); describe('isUrl', () => { it('isUrl test', () => { From c9275b2d0b740cfc6ed7e99578e7debd1b35f3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 22 Mar 2023 10:04:47 +0100 Subject: [PATCH 305/326] Update changelog relese 16.18.0 (#4596) --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81db5d5a22..1fe6548560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,24 @@ - Use a universal static path for both documentation and volto repos. @stevepiercy [#4376](https://github.com/plone/volto/issues/4376) +## 16.18.0 (2023-03-22) + +### Feature + +- Add Vale to CI for spell and style checks. @MAX-786 [#4423](https://github.com/plone/volto/issues/4423) + +### Bugfix + +- Patch updates for some dependencies. @davisagli [#4520](https://github.com/plone/volto/issues/4520) +- InternalURl helper method should incorporate externalRoutes settings into consideration. @iFlameing [#4559](https://github.com/plone/volto/issues/4559) +- Update message add-on control panel: remove 'buildout', update reference. @ksuess [#4574](https://github.com/plone/volto/issues/4574) + +### Documentation + +- Fix broken links at ReactJS.org. @stevepiercy [#4569](https://github.com/plone/volto/issues/4569) +- Fix video warnings and link errors. @stevepiercy [#4578](https://github.com/plone/volto/issues/4578) + + ## 16.17.1 (2023-03-16) ### Bugfix From 0cca3b257b9fc98da773d3aa8f73a923458a9b59 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Wed, 22 Mar 2023 10:06:22 +0100 Subject: [PATCH 306/326] Release 17.0.0-alpha.3 --- CHANGELOG.md | 20 ++++++++++++++++++++ news/4423.feature | 1 - news/4523.documentation | 1 - news/4526.bugfix | 1 - news/4555.documentation | 1 - news/4559.bugfix | 1 - news/4569.documentation | 1 - news/4574.bugfix | 1 - news/4578.documentation | 1 - package.json | 2 +- packages/volto-slate/package.json | 2 +- 11 files changed, 22 insertions(+), 10 deletions(-) delete mode 100644 news/4423.feature delete mode 100644 news/4523.documentation delete mode 100644 news/4526.bugfix delete mode 100644 news/4555.documentation delete mode 100644 news/4559.bugfix delete mode 100644 news/4569.documentation delete mode 100644 news/4574.bugfix delete mode 100644 news/4578.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe6548560..074b7a462a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,26 @@ +## 17.0.0-alpha.3 (2023-03-22) + +### Feature + +- Add Vale to CI for spell and style checks. @MAX-786 [#4423](https://github.com/plone/volto/issues/4423) + +### Bugfix + +- Fix Search is case sensitive in Block chooser @iRohitSingh [#4526](https://github.com/plone/volto/issues/4526) +- InternalURl helper method should incorporate externalRoutes settings into consideration. @iFlameing [#4559](https://github.com/plone/volto/issues/4559) +- Update message add-on control panel: remove 'buildout', update reference. @ksuess [#4574](https://github.com/plone/volto/issues/4574) + +### Documentation + +- Deleted duplicate import and fixed training URLs. @yahya-cloud [#4523](https://github.com/plone/volto/issues/4523) +- Fix grammar in PR #4542. @stevepiercy [#4555](https://github.com/plone/volto/issues/4555) +- Fix broken links at ReactJS.org. @stevepiercy [#4569](https://github.com/plone/volto/issues/4569) +- Fix video warnings and link errors. @stevepiercy [#4578](https://github.com/plone/volto/issues/4578) + + ## 17.0.0-alpha.2 (2023-03-15) ### Breaking diff --git a/news/4423.feature b/news/4423.feature deleted file mode 100644 index 8901b62863..0000000000 --- a/news/4423.feature +++ /dev/null @@ -1 +0,0 @@ -Add Vale to CI for spell and style checks. @MAX-786 diff --git a/news/4523.documentation b/news/4523.documentation deleted file mode 100644 index 9236a1fd29..0000000000 --- a/news/4523.documentation +++ /dev/null @@ -1 +0,0 @@ -Deleted duplicate import and fixed training URLs. @yahya-cloud \ No newline at end of file diff --git a/news/4526.bugfix b/news/4526.bugfix deleted file mode 100644 index a4f5ed0bdb..0000000000 --- a/news/4526.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix Search is case sensitive in Block chooser @iRohitSingh \ No newline at end of file diff --git a/news/4555.documentation b/news/4555.documentation deleted file mode 100644 index 75b7876611..0000000000 --- a/news/4555.documentation +++ /dev/null @@ -1 +0,0 @@ -Fix grammar in PR #4542. @stevepiercy diff --git a/news/4559.bugfix b/news/4559.bugfix deleted file mode 100644 index 395e77aca0..0000000000 --- a/news/4559.bugfix +++ /dev/null @@ -1 +0,0 @@ -InternalURl helper method should incorporate externalRoutes settings into consideration. @iFlameing \ No newline at end of file diff --git a/news/4569.documentation b/news/4569.documentation deleted file mode 100644 index a08842d90d..0000000000 --- a/news/4569.documentation +++ /dev/null @@ -1 +0,0 @@ -Fix broken links at ReactJS.org. @stevepiercy diff --git a/news/4574.bugfix b/news/4574.bugfix deleted file mode 100644 index e5013ab087..0000000000 --- a/news/4574.bugfix +++ /dev/null @@ -1 +0,0 @@ -Update message add-on control panel: remove 'buildout', update reference. @ksuess \ No newline at end of file diff --git a/news/4578.documentation b/news/4578.documentation deleted file mode 100644 index 9a6645de55..0000000000 --- a/news/4578.documentation +++ /dev/null @@ -1 +0,0 @@ -Fix video warnings and link errors. @stevepiercy diff --git a/package.json b/package.json index 3fb6c324a1..ccc3202d9c 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "17.0.0-alpha.2", + "version": "17.0.0-alpha.3", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" diff --git a/packages/volto-slate/package.json b/packages/volto-slate/package.json index d6a6dc6f68..6b1160981d 100644 --- a/packages/volto-slate/package.json +++ b/packages/volto-slate/package.json @@ -1,6 +1,6 @@ { "name": "@plone/volto-slate", - "version": "17.0.0-alpha.2", + "version": "17.0.0-alpha.3", "description": "Slate.js integration with Volto", "main": "src/index.js", "author": "European Environment Agency: IDM2 A-Team", From 4e800aa670aaba6ba3196e21c31e52760f7e287c Mon Sep 17 00:00:00 2001 From: Andrea Cecchi Date: Thu, 23 Mar 2023 16:27:40 +0100 Subject: [PATCH 307/326] Fix regexp that checks valid URLs and improve tests (#4601) --- news/4601.bugfix | 1 + src/helpers/FormValidation/FormValidation.js | 11 ++++- .../FormValidation/FormValidation.test.js | 41 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 news/4601.bugfix diff --git a/news/4601.bugfix b/news/4601.bugfix new file mode 100644 index 0000000000..cbb24473ca --- /dev/null +++ b/news/4601.bugfix @@ -0,0 +1 @@ +Fix regexp that checks valid URLs and improve tests [cekk] diff --git a/src/helpers/FormValidation/FormValidation.js b/src/helpers/FormValidation/FormValidation.js index 7c6d609f19..0c5fd28eac 100644 --- a/src/helpers/FormValidation/FormValidation.js +++ b/src/helpers/FormValidation/FormValidation.js @@ -65,7 +65,16 @@ const widgetValidation = { }, url: { isValidURL: (urlValue, urlObj, intlFunc) => { - const urlRegex = /^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?|^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([_.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?|^((http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gm; + var urlRegex = new RegExp( + '^(https?:\\/\\/)?' + // validate protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // validate domain name + '((\\d{1,3}\\.){3}\\d{1,3}))|' + // validate OR ip (v4) address + '(localhost)' + // validate OR localhost address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // validate port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // validate query string + '(\\#[-a-z\\d_]*)?$', // validate fragment locator + 'i', + ); const isValid = urlRegex.test(urlValue); return !isValid ? intlFunc(messages.isValidURL) : null; }, diff --git a/src/helpers/FormValidation/FormValidation.test.js b/src/helpers/FormValidation/FormValidation.test.js index 56da3a43a3..df25ac0038 100644 --- a/src/helpers/FormValidation/FormValidation.test.js +++ b/src/helpers/FormValidation/FormValidation.test.js @@ -5,6 +5,7 @@ const schema = { properties: { username: { title: 'Username', type: 'string', description: '' }, email: { title: 'Email', type: 'string', widget: 'email', description: '' }, + url: { title: 'url', type: 'string', widget: 'url', description: '' }, }, fieldsets: [ { id: 'default', title: 'FIXME: User Data', fields: ['username'] }, @@ -87,5 +88,45 @@ describe('FormValidation', () => { }), ).toEqual({}); }); + it('validates incorrect url', () => { + formData.url = 'foo'; + expect( + FormValidation.validateFieldsPerFieldset({ + schema, + formData, + formatMessage, + }), + ).toEqual({ url: [messages.isValidURL.defaultMessage] }); + }); + it('validates url', () => { + formData.url = 'https://plone.org/'; + expect( + FormValidation.validateFieldsPerFieldset({ + schema, + formData, + formatMessage, + }), + ).toEqual({}); + }); + it('validates url with ip', () => { + formData.url = 'http://127.0.0.1:8080/Plone'; + expect( + FormValidation.validateFieldsPerFieldset({ + schema, + formData, + formatMessage, + }), + ).toEqual({}); + }); + it('validates url with localhost', () => { + formData.url = 'http://localhost:8080/Plone'; + expect( + FormValidation.validateFieldsPerFieldset({ + schema, + formData, + formatMessage, + }), + ).toEqual({}); + }); }); }); From fbd4fe5c4465dee265b4c1921a180bbacfe52b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Katja=20S=C3=BCss?= Date: Thu, 23 Mar 2023 16:31:03 +0100 Subject: [PATCH 308/326] DefaultView (blocks disabled): Show field name as tip on hover of label (#4598) --- news/4598.feature | 1 + src/components/theme/View/DefaultView.jsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 news/4598.feature diff --git a/news/4598.feature b/news/4598.feature new file mode 100644 index 0000000000..7c7160922f --- /dev/null +++ b/news/4598.feature @@ -0,0 +1 @@ +DefaultView (view of fields for content types with blocks disabled): Show field name as tip on hover of label. @ksuess \ No newline at end of file diff --git a/src/components/theme/View/DefaultView.jsx b/src/components/theme/View/DefaultView.jsx index cf34ab3cf5..c2db9de92b 100644 --- a/src/components/theme/View/DefaultView.jsx +++ b/src/components/theme/View/DefaultView.jsx @@ -87,7 +87,7 @@ const DefaultView = (props) => { return f !== 'title' ? ( - + From 33962f130f25a92760848c07ab59bcd13a2ef37d Mon Sep 17 00:00:00 2001 From: David Glick Date: Thu, 23 Mar 2023 11:26:34 -0700 Subject: [PATCH 309/326] Update simple-git (#4546) --- packages/scripts/news/4546.breaking | 1 + packages/scripts/package.json | 3 +- packages/scripts/yarn.lock | 47 ++++++++++++----------------- yarn.lock | 6 ++-- 4 files changed, 24 insertions(+), 33 deletions(-) create mode 100644 packages/scripts/news/4546.breaking diff --git a/packages/scripts/news/4546.breaking b/packages/scripts/news/4546.breaking new file mode 100644 index 0000000000..468d189887 --- /dev/null +++ b/packages/scripts/news/4546.breaking @@ -0,0 +1 @@ +Remove dependency on `simple-git`. It is used by `mrs-developer` but not directly. @davisagli diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 95c68bb612..2936321d4f 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -68,8 +68,7 @@ "fs-extra": "10.1.0", "git-url-parse": "^11.6.0", "mrs-developer": "*", - "pofile": "1.0.10", - "simple-git": "3.15.0" + "pofile": "1.0.10" }, "devDependencies": { "release-it": "14.2.1" diff --git a/packages/scripts/yarn.lock b/packages/scripts/yarn.lock index 36c5d848fe..778e6ca55c 100644 --- a/packages/scripts/yarn.lock +++ b/packages/scripts/yarn.lock @@ -1941,7 +1941,6 @@ __metadata: mrs-developer: "*" pofile: 1.0.10 release-it: 14.2.1 - simple-git: 3.15.0 bin: addon: ./addon/index.js changelogupdater: ./changelogupdater.cjs @@ -2183,6 +2182,13 @@ __metadata: languageName: node linkType: hard +"async@npm:^3.2.4": + version: 3.2.4 + resolution: "async@npm:3.2.4" + checksum: 43d07459a4e1d09b84a20772414aa684ff4de085cbcaec6eea3c7a8f8150e8c62aa6cd4e699fe8ee93c3a5b324e777d34642531875a0817a35697522c1b02e89 + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -2464,7 +2470,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0": +"chalk@npm:^2.0.0, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -2577,13 +2583,6 @@ __metadata: languageName: node linkType: hard -"colors@npm:^1.4.0": - version: 1.4.0 - resolution: "colors@npm:1.4.0" - checksum: 98aa2c2418ad87dedf25d781be69dc5fc5908e279d9d30c34d8b702e586a0474605b3a189511482b9d5ed0d20c867515d22749537f7bc546256c6014f3ebdcec - languageName: node - linkType: hard - "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -2683,7 +2682,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.0.1, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.4": +"debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" dependencies: @@ -3840,14 +3839,15 @@ __metadata: linkType: hard "mrs-developer@npm:*": - version: 1.6.0 - resolution: "mrs-developer@npm:1.6.0" + version: 2.1.0 + resolution: "mrs-developer@npm:2.1.0" dependencies: - colors: ^1.4.0 - simple-git: ^1.132.0 + async: ^3.2.4 + chalk: ^2.4.2 + simple-git: ^3.3.0 bin: missdev: src/command.js - checksum: 7154612bfa0f316b7a443bf4bc698bf46ba953900a5f134c67048974134a678218a0624e92c81ec1d84f5b923593b6a2c0d768a72d6ea4199fa769a30fe9530a + checksum: 11a47dee219fb4986a591195ea18d47602741f90d66ccb94378d619979c0bc52284590cc11f48da60fbbd463765904fc60a3800fb5f786caa9c5a5610cbece86 languageName: node linkType: hard @@ -4609,23 +4609,14 @@ __metadata: languageName: node linkType: hard -"simple-git@npm:3.15.0": - version: 3.15.0 - resolution: "simple-git@npm:3.15.0" +"simple-git@npm:^3.3.0": + version: 3.17.0 + resolution: "simple-git@npm:3.17.0" dependencies: "@kwsites/file-exists": ^1.1.1 "@kwsites/promise-deferred": ^1.1.1 debug: ^4.3.4 - checksum: 4733d1b769965a7608254c3a5b27532e02e60d4369bc9b397ce47159dd153344a3774739fd2602f693947ed64631b30145b970988fb007b5b779459e9abcdee8 - languageName: node - linkType: hard - -"simple-git@npm:^1.132.0": - version: 1.132.0 - resolution: "simple-git@npm:1.132.0" - dependencies: - debug: ^4.0.1 - checksum: 717d7cf7919ebe771b2562ed70c79c3971ac650d9577a15c47052472f8e549a088aea593b6a7245d73af0425a202106e6c236d35a73dc4c48dce99bf7b4f939a + checksum: 977a05cb0b5087296348b5afa682ce552f43234f5fd29b44c3d7f56b3682d10dcb03752a418e508aaffcbdb6ea2e304a3ef10095197d6743d2353adb85f32592 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index 520b246b8a..f5aa1197cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21874,13 +21874,13 @@ __metadata: linkType: hard "simple-git@npm:^3.3.0": - version: 3.15.1 - resolution: "simple-git@npm:3.15.1" + version: 3.17.0 + resolution: "simple-git@npm:3.17.0" dependencies: "@kwsites/file-exists": ^1.1.1 "@kwsites/promise-deferred": ^1.1.1 debug: ^4.3.4 - checksum: 2b97c3ba3c0709eded408013336bf1822be4171d965f3ab45f5bfc05ec690f9e488e11280bec8e2faa05d3039c4769e9d8197040a48dc5cf21cc5c7fa22fe77f + checksum: 977a05cb0b5087296348b5afa682ce552f43234f5fd29b44c3d7f56b3682d10dcb03752a418e508aaffcbdb6ea2e304a3ef10095197d6743d2353adb85f32592 languageName: node linkType: hard From 76bf09d6efcf5725c5834de95750b6adad356ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Fri, 31 Mar 2023 13:02:19 +0200 Subject: [PATCH 310/326] Set sameSite in `18N_LANGUAGE` cookie (#4627) --- news/4627.feature | 1 + src/actions/language/language.js | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/4627.feature diff --git a/news/4627.feature b/news/4627.feature new file mode 100644 index 0000000000..dd91379ab9 --- /dev/null +++ b/news/4627.feature @@ -0,0 +1 @@ +Set sameSite in I18N_LANGUAGE cookie @sneridagh diff --git a/src/actions/language/language.js b/src/actions/language/language.js index 6ea855c8b2..e62d4be472 100644 --- a/src/actions/language/language.js +++ b/src/actions/language/language.js @@ -7,6 +7,7 @@ export function changeLanguageCookies(language, req) { const cookieOptions = getCookieOptions({ secure: req?.protocol?.startsWith('https') ? true : false, + sameSite: 'strict', }); if (!req) { From 010696643a2c04ce89d110fb15b1e18f9a428c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Fri, 31 Mar 2023 13:02:56 +0200 Subject: [PATCH 311/326] Pining of `pydata-sphinx-theme` and `sphinx-book-theme`, CI is complaining. (#4626) --- news/4626.documentation | 1 + requirements-docs.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 news/4626.documentation diff --git a/news/4626.documentation b/news/4626.documentation new file mode 100644 index 0000000000..7898850040 --- /dev/null +++ b/news/4626.documentation @@ -0,0 +1 @@ +Fix documentation build, add pins @sneridagh diff --git a/requirements-docs.txt b/requirements-docs.txt index 7c4e331ab9..d4596cb251 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -5,7 +5,8 @@ lesscpy linkify-it-py myst-parser sphinx-autobuild -sphinx-book-theme +pydata-sphinx-theme<=0.8.99 +sphinx-book-theme==0.3.3 sphinx-copybutton sphinx-sitemap sphinx-togglebutton From 5808b787230d4049632a4d5b14c3b0a80cc86e77 Mon Sep 17 00:00:00 2001 From: David Glick Date: Fri, 31 Mar 2023 12:01:02 -0700 Subject: [PATCH 312/326] Trigger CI on pull_request event (#4629) I chatted with Maurits this afternoon as well, he agrees on the pull_request trigger. Let's see first how this works out and improve on what else we might find. Thanks for the extra feedback @wesleybl --- .github/workflows/acceptance.yml | 12 +++++++++++- .github/workflows/code-analysis.yml | 5 ++++- .github/workflows/unit.yml | 3 ++- news/4629.internal | 1 + 4 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 news/4629.internal diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 41309bc3b6..cea3058c29 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -1,7 +1,8 @@ name: Acceptance Tests -on: [push] +on: [push, pull_request] jobs: core: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name runs-on: ubuntu-latest name: Core Basic timeout-minutes: 45 @@ -56,6 +57,7 @@ jobs: path: cypress/videos coreblocks: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name runs-on: ubuntu-latest name: Core Blocks timeout-minutes: 35 @@ -110,6 +112,7 @@ jobs: path: cypress/videos corevoltoslate: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name runs-on: ubuntu-latest name: Core Volto Slate timeout-minutes: 45 @@ -164,6 +167,7 @@ jobs: path: cypress/videos core5: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name runs-on: ubuntu-latest name: Core Basic - Plone 5 strategy: @@ -217,6 +221,7 @@ jobs: path: cypress/videos coresandbox: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name runs-on: ubuntu-latest name: Coresandbox timeout-minutes: 35 @@ -270,6 +275,7 @@ jobs: path: cypress/videos guillotina: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: Guillotina runs-on: ubuntu-latest timeout-minutes: 35 @@ -325,6 +331,7 @@ jobs: path: cypress/videos multilingual: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: Multilingual runs-on: ubuntu-latest timeout-minutes: 35 @@ -379,6 +386,7 @@ jobs: path: cypress/videos workingcopy: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: Working Copy runs-on: ubuntu-latest timeout-minutes: 35 @@ -458,6 +466,7 @@ jobs: path: cypress/videos generator: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: Project Generator runs-on: ubuntu-latest timeout-minutes: 35 @@ -553,6 +562,7 @@ jobs: working-directory: ${{env.generator-directory}} seamless: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: Seamless Mode runs-on: ubuntu-latest timeout-minutes: 35 diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml index 66ad61e73b..9b4db344de 100644 --- a/.github/workflows/code-analysis.yml +++ b/.github/workflows/code-analysis.yml @@ -1,7 +1,8 @@ name: Code Analysis Check -on: [push] +on: [push, pull_request] jobs: prettier: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: Prettier runs-on: ubuntu-latest strategy: @@ -24,6 +25,7 @@ jobs: run: yarn run prettier eslint: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ESlint runs-on: ubuntu-latest strategy: @@ -46,6 +48,7 @@ jobs: run: yarn run lint i18n: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: i18n runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 36880bd257..97428e6fe3 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -1,7 +1,8 @@ name: Unit Tests -on: [push] +on: [push, pull_request] jobs: unit: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: Core Unit Tests runs-on: ubuntu-latest strategy: diff --git a/news/4629.internal b/news/4629.internal new file mode 100644 index 0000000000..869b4a6d24 --- /dev/null +++ b/news/4629.internal @@ -0,0 +1 @@ +Trigger CI workflows to run from external pull requests. @davisagli From dac283a02396bcd88360b923fa3f9747660b936f Mon Sep 17 00:00:00 2001 From: Fred van Dijk Date: Fri, 31 Mar 2023 21:05:03 +0200 Subject: [PATCH 313/326] developer process for first time contributing (#4617) Co-authored-by: Maurits van Rees --- .../developer-guidelines/contributing.md | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/source/developer-guidelines/contributing.md b/docs/source/developer-guidelines/contributing.md index 42568ce6e6..04446bf2db 100644 --- a/docs/source/developer-guidelines/contributing.md +++ b/docs/source/developer-guidelines/contributing.md @@ -33,10 +33,32 @@ In your report, please specify a few things: ```{include} ./branch-policy.md ``` -## Create a pull request +## Sign and send us the Plone Contributors Agreement You must sign the [Plone Contributor Agreement](https://plone.org/foundation/contributors-agreement) to contribute code and documentation to any Plone project. -This means that we can NOT accept pull requests from you until you do this. +This means that we can NOT accept pull requests from you from any location until you do this. + + +## First Time Contributors: work from a fork + +When we have received the Plone Contributor Agreement and it has been process by a community member, you will be added to the Plone organisation and added to the Contributors Team on GitHub. +You can create issues, add comments to existing issues, and discuss the development of Plone with others. +Creating branches and editing code on our main repositories is restricted and granted to members who have been active in the Plone community for a while and have shown continued interest to contributing. + +- First, verify that your issue is valid by creating it and discussing it with other developers. +- Then, create a fork of the repo in your own workspace, work on your code and make commits. +- When you want to run our code quality checks, create a Pull Request from your repo/branch to the main repository. +- As a security measure, the first run of code Quality checks need to be [approved by a member in our Developers Team](https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks) +- A Developer with full access to the workflow will need to review and approve your Pull request. + +Please be aware that as long as you are working from a fork, you are the only developer being able to commit to your branch. +Collaboration between developers on bigger pull requests is more efficient when these are done on branch on the main repo. +Therefore: as your first time contribution don't choose a too big problem to solve. +Pick something that you can fix or complete from start to end. + + + +## Documenting your changes All pull requests must include a `towncrier` news item. This is a file that is placed in the root of the repository directory at `/news`. @@ -57,7 +79,8 @@ strings as translatable as defined in the [i18n guide](../recipes/i18n.md). ## Code Quality All pull requests must pass tests, documentation builds, and other code quality checks. -Contributors are strongly encouraged to run these checks locally before creating a pull request. +Developers are strongly encouraged to run these checks locally before creating a pull request. +Contributors without full Developer access will need to create a Pull request first and get approval for their first PR from a Developer. These checks are enforced automatically on every pull request, so you might as well save time and frustration by doing these checks locally first. Specifically: From cb9dfd062cf0c51af23b30afeb10f51634e36327 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhardwaj <67866657+utkkkarshhh@users.noreply.github.com> Date: Mon, 3 Apr 2023 15:50:01 +0530 Subject: [PATCH 314/326] 3092 improve spellcheck (#4633) Co-authored-by: Steve Piercy --- news/3092.documentation | 1 + styles/Vocab/Plone/accept.txt | 2 ++ styles/Vocab/Plone/reject.txt | 5 +++++ 3 files changed, 8 insertions(+) create mode 100644 news/3092.documentation diff --git a/news/3092.documentation b/news/3092.documentation new file mode 100644 index 0000000000..2f1b9f34d6 --- /dev/null +++ b/news/3092.documentation @@ -0,0 +1 @@ +Added `JavaScript` and `NodeJS` as accepted spellings, and deviations of them as rejected spellings. @utkkkarshhh \ No newline at end of file diff --git a/styles/Vocab/Plone/accept.txt b/styles/Vocab/Plone/accept.txt index 78bcf7537b..0ff684308f 100644 --- a/styles/Vocab/Plone/accept.txt +++ b/styles/Vocab/Plone/accept.txt @@ -6,3 +6,5 @@ Razzle RichText Volto Zope +JavaScript +NodeJS diff --git a/styles/Vocab/Plone/reject.txt b/styles/Vocab/Plone/reject.txt index e69de29bb2..1deaa26744 100644 --- a/styles/Vocab/Plone/reject.txt +++ b/styles/Vocab/Plone/reject.txt @@ -0,0 +1,5 @@ +node +nodejs +javascript +js +Javascript \ No newline at end of file From ba40abf6b7ca48afabbef57b8623ba5329df273c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 3 Apr 2023 13:59:22 +0200 Subject: [PATCH 315/326] Update to latest Razzle - needed since #3997. This fixes the duplicated Razzles issue (#4640) --- .../generator-volto/generators/app/templates/package.json.tpl | 2 +- packages/generator-volto/news/4640.bugfix | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 packages/generator-volto/news/4640.bugfix diff --git a/packages/generator-volto/generators/app/templates/package.json.tpl b/packages/generator-volto/generators/app/templates/package.json.tpl index eeea34b12f..8292f576f9 100644 --- a/packages/generator-volto/generators/app/templates/package.json.tpl +++ b/packages/generator-volto/generators/app/templates/package.json.tpl @@ -148,7 +148,7 @@ "@storybook/addon-essentials": "^6.3.0", "@storybook/addon-links": "^6.3.0", "@storybook/react": "^6.3.0", - "razzle": "4.2.17", + "razzle": "4.2.18", "stylelint": "14.0.1", "stylelint-config-idiomatic-order": "8.1.0", "stylelint-config-prettier": "8.0.1", diff --git a/packages/generator-volto/news/4640.bugfix b/packages/generator-volto/news/4640.bugfix new file mode 100644 index 0000000000..aecf02d3b1 --- /dev/null +++ b/packages/generator-volto/news/4640.bugfix @@ -0,0 +1 @@ +Update to latest Razzle - needed since #3997. This fixes the duplicated Razzles issue @sneridagh From bc564db6798c98c7ef94d1ffc32e1b3d94d516a5 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Mon, 3 Apr 2023 14:00:46 +0200 Subject: [PATCH 316/326] Release generate-volto 7.0.0-alpha.3 --- packages/generator-volto/CHANGELOG.md | 7 +++++++ packages/generator-volto/news/4640.bugfix | 1 - packages/generator-volto/package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) delete mode 100644 packages/generator-volto/news/4640.bugfix diff --git a/packages/generator-volto/CHANGELOG.md b/packages/generator-volto/CHANGELOG.md index e3b7cc11cd..b46cda34cd 100644 --- a/packages/generator-volto/CHANGELOG.md +++ b/packages/generator-volto/CHANGELOG.md @@ -8,6 +8,13 @@ +## 7.0.0-alpha.3 (2023-04-03) + +### Bugfix + +- Update to latest Razzle - needed since #3997. This fixes the duplicated Razzles issue @sneridagh [#4640](https://github.com/plone/volto/issues/4640) + + ## 7.0.0-alpha.2 (2023-03-05) ### Feature diff --git a/packages/generator-volto/news/4640.bugfix b/packages/generator-volto/news/4640.bugfix deleted file mode 100644 index aecf02d3b1..0000000000 --- a/packages/generator-volto/news/4640.bugfix +++ /dev/null @@ -1 +0,0 @@ -Update to latest Razzle - needed since #3997. This fixes the duplicated Razzles issue @sneridagh diff --git a/packages/generator-volto/package.json b/packages/generator-volto/package.json index 022f0bc334..3a9ac534fe 100644 --- a/packages/generator-volto/package.json +++ b/packages/generator-volto/package.json @@ -10,7 +10,7 @@ } ], "license": "MIT", - "version": "7.0.0-alpha.2", + "version": "7.0.0-alpha.3", "repository": { "type": "git", "url": "git+https://github.com/plone/generator-volto.git" From 169a01bbaa024518253b7993676dcc7f4dd3bec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Mon, 3 Apr 2023 17:20:37 +0200 Subject: [PATCH 317/326] Razzle upgrade notice in upgrade guide (#4641) Co-authored-by: Steve Piercy --- docs/source/upgrade-guide/index.md | 5 +++++ news/4641.documentation | 1 + 2 files changed, 6 insertions(+) create mode 100644 news/4641.documentation diff --git a/docs/source/upgrade-guide/index.md b/docs/source/upgrade-guide/index.md index 166f10263c..31bd17f7f9 100644 --- a/docs/source/upgrade-guide/index.md +++ b/docs/source/upgrade-guide/index.md @@ -47,6 +47,11 @@ Volto 17 now uses Webpack 5. If you customized `razzle.config.js` for your project to change Webpack configuration or use Webpack plugins, you might need to make adjustments. +### Razzle upgraded to version `4.2.18` + +Razzle has been upgraded to version `4.2.18`. +It is recommended that you update your project's dependency on Razzle to this version in order to avoid duplication. + ### `BlockChooser` component now uses `popperjs` internally Technically not a breaking, the API nor the component contract has changed, but it's worth noting this change in here. diff --git a/news/4641.documentation b/news/4641.documentation new file mode 100644 index 0000000000..335c2489fd --- /dev/null +++ b/news/4641.documentation @@ -0,0 +1 @@ +Razzle upgrade notice in upgrade guide @sneridagh From 72ba0818a7a9eb6a957d6f07f1f166a24b9f717a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20de=20Alba?= Date: Wed, 5 Apr 2023 10:32:22 +0200 Subject: [PATCH 318/326] Release notes for 16.19.0 (#4655) --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074b7a462a..c492f428b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,23 @@ - Use a universal static path for both documentation and volto repos. @stevepiercy [#4376](https://github.com/plone/volto/issues/4376) +## 16.19.0 (2023-04-04) + +### Feature + +- DefaultView (view of fields for content types with blocks disabled): Show field name as tip on hover of label. @ksuess [#4598](https://github.com/plone/volto/issues/4598) +- Set sameSite in I18N_LANGUAGE cookie @sneridagh [#4627](https://github.com/plone/volto/issues/4627) + +### Bugfix + +- Fix regexp that checks valid URLs and improve tests [cekk] [#4601](https://github.com/plone/volto/issues/4601) + +### Documentation + +- Added `JavaScript` and `NodeJS` as accepted spellings, and deviations of them as rejected spellings. @utkkkarshhh [#3092](https://github.com/plone/volto/issues/3092) +- Fix documentation build, add pins @sneridagh [#4626](https://github.com/plone/volto/issues/4626) + + ## 16.18.0 (2023-03-22) ### Feature From 267181c1eaeb450844392b3be13b1f0aa308f5ef Mon Sep 17 00:00:00 2001 From: iRohitSingh <61353484+iRohitSingh@users.noreply.github.com> Date: Wed, 5 Apr 2023 19:20:37 +0530 Subject: [PATCH 319/326] Fixed wrong localization on password reset page(#4656) (#4657) --- locales/ca/LC_MESSAGES/volto.po | 4 ++-- locales/de/LC_MESSAGES/volto.po | 8 ++++---- locales/en/LC_MESSAGES/volto.po | 4 ++-- locales/es/LC_MESSAGES/volto.po | 4 ++-- locales/eu/LC_MESSAGES/volto.po | 4 ++-- locales/fr/LC_MESSAGES/volto.po | 4 ++-- locales/it/LC_MESSAGES/volto.po | 4 ++-- locales/ja/LC_MESSAGES/volto.po | 4 ++-- locales/nl/LC_MESSAGES/volto.po | 4 ++-- locales/pt/LC_MESSAGES/volto.po | 4 ++-- locales/pt_BR/LC_MESSAGES/volto.po | 4 ++-- locales/ro/LC_MESSAGES/volto.po | 4 ++-- locales/volto.pot | 6 +++--- locales/zh_CN/LC_MESSAGES/volto.po | 4 ++-- news/4656.bugfix | 1 + src/components/theme/PasswordReset/PasswordReset.jsx | 2 +- .../theme/PasswordReset/RequestPasswordReset.jsx | 2 +- .../__snapshots__/RequestPasswordReset.test.jsx.snap | 2 +- 18 files changed, 35 insertions(+), 34 deletions(-) create mode 100644 news/4656.bugfix diff --git a/locales/ca/LC_MESSAGES/volto.po b/locales/ca/LC_MESSAGES/volto.po index 1133771312..d204173163 100644 --- a/locales/ca/LC_MESSAGES/volto.po +++ b/locales/ca/LC_MESSAGES/volto.po @@ -2098,7 +2098,7 @@ msgid "My email is" msgstr "" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "El meu nom d'usuari és" @@ -4193,7 +4193,7 @@ msgid "label_my_email_is" msgstr "" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "El meu nom d'usuari és" diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index 8a717bc3d2..908ad7614d 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -2095,9 +2095,9 @@ msgid "My email is" msgstr "Meine E-Mail ist" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" -msgstr "Mein Benutzername ist" +msgstr "Mein Nutzername lautet" #: components/manage/Sharing/Sharing #: components/theme/ContactForm/ContactForm @@ -4190,9 +4190,9 @@ msgid "label_my_email_is" msgstr "Meine E-Mail ist" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" -msgstr "El meu nom d'usuari és" +msgstr "Mein Nutzername lautet" #: config/Blocks # defaultMessage: Lead Image Field diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 30e4e39d71..40dc8bc6b1 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -2089,7 +2089,7 @@ msgid "My email is" msgstr "" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "" @@ -4184,7 +4184,7 @@ msgid "label_my_email_is" msgstr "" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index 5eaf110016..076b32ae41 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -2100,7 +2100,7 @@ msgid "My email is" msgstr "Mi dirección de correo electrónico es" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Mi nombre de usuario es" @@ -4195,7 +4195,7 @@ msgid "label_my_email_is" msgstr "Mi dirección de correo es" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Mi nombre de usuario es" diff --git a/locales/eu/LC_MESSAGES/volto.po b/locales/eu/LC_MESSAGES/volto.po index 4f058ed3ab..fc08930c3d 100644 --- a/locales/eu/LC_MESSAGES/volto.po +++ b/locales/eu/LC_MESSAGES/volto.po @@ -2096,7 +2096,7 @@ msgid "My email is" msgstr "Nire helbidea hauxe da" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Nire erabiltzaile izena da" @@ -4191,7 +4191,7 @@ msgid "label_my_email_is" msgstr "Nire helbidea hauxe da: " #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Nire erabiltzaile izena da" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 5f9097b014..786de2a3f2 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -2106,7 +2106,7 @@ msgid "My email is" msgstr "Mon email est" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Mon nom d'utilisateur est" @@ -4201,7 +4201,7 @@ msgid "label_my_email_is" msgstr "Mon email est" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Mon nom d'utilisateur est" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index ee1c27901c..8139c852c5 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -2089,7 +2089,7 @@ msgid "My email is" msgstr "La mia email è" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Il mio nome utente è" @@ -4184,7 +4184,7 @@ msgid "label_my_email_is" msgstr "La mia email è" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Il mio nome utente è" diff --git a/locales/ja/LC_MESSAGES/volto.po b/locales/ja/LC_MESSAGES/volto.po index 53ba075dea..501902a95f 100644 --- a/locales/ja/LC_MESSAGES/volto.po +++ b/locales/ja/LC_MESSAGES/volto.po @@ -2097,7 +2097,7 @@ msgid "My email is" msgstr "" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "私のユーザー名は" @@ -4192,7 +4192,7 @@ msgid "label_my_email_is" msgstr "" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "私のユーザー名は" diff --git a/locales/nl/LC_MESSAGES/volto.po b/locales/nl/LC_MESSAGES/volto.po index 17eac461c8..53effede9e 100644 --- a/locales/nl/LC_MESSAGES/volto.po +++ b/locales/nl/LC_MESSAGES/volto.po @@ -2096,7 +2096,7 @@ msgid "My email is" msgstr "Mijn e-mail is" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Mijn gebruikersnaam is" @@ -4191,7 +4191,7 @@ msgid "label_my_email_is" msgstr "Mijn e-mail is" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Mijn gebruikersnaam is" diff --git a/locales/pt/LC_MESSAGES/volto.po b/locales/pt/LC_MESSAGES/volto.po index 40dae41756..6f2139d880 100644 --- a/locales/pt/LC_MESSAGES/volto.po +++ b/locales/pt/LC_MESSAGES/volto.po @@ -2097,7 +2097,7 @@ msgid "My email is" msgstr "" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Meu nome de usuário é" @@ -4192,7 +4192,7 @@ msgid "label_my_email_is" msgstr "" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Meu nome de usuário é" diff --git a/locales/pt_BR/LC_MESSAGES/volto.po b/locales/pt_BR/LC_MESSAGES/volto.po index d4d8f10d1e..0087d92fbe 100644 --- a/locales/pt_BR/LC_MESSAGES/volto.po +++ b/locales/pt_BR/LC_MESSAGES/volto.po @@ -2099,7 +2099,7 @@ msgid "My email is" msgstr "Meu e-mail é" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Meu nome de usuário é" @@ -4194,7 +4194,7 @@ msgid "label_my_email_is" msgstr "Meu endereço de e-mail é" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Meu nome de usuário é<<<<<<< HEAD" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index 88cc55fcdb..45e761e855 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -2089,7 +2089,7 @@ msgid "My email is" msgstr "" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "Numele meu de utilizator este" @@ -4184,7 +4184,7 @@ msgid "label_my_email_is" msgstr "" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "Numele meu de utilizator este" diff --git a/locales/volto.pot b/locales/volto.pot index ad763942a7..660ab3fe0f 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2023-03-01T10:46:45.512Z\n" +"POT-Creation-Date: 2023-04-05T10:12:20.363Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -2091,7 +2091,7 @@ msgid "My email is" msgstr "" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "" @@ -4186,7 +4186,7 @@ msgid "label_my_email_is" msgstr "" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "" diff --git a/locales/zh_CN/LC_MESSAGES/volto.po b/locales/zh_CN/LC_MESSAGES/volto.po index ac6774f8e8..3d436e9e67 100644 --- a/locales/zh_CN/LC_MESSAGES/volto.po +++ b/locales/zh_CN/LC_MESSAGES/volto.po @@ -2095,7 +2095,7 @@ msgid "My email is" msgstr "我的邮箱是" #: components/theme/PasswordReset/PasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "My username is" msgstr "我的用户名是" @@ -4190,7 +4190,7 @@ msgid "label_my_email_is" msgstr "我的邮箱是" #: components/theme/PasswordReset/RequestPasswordReset -# defaultMessage: My username is +# defaultMessage: My user name is msgid "label_my_username_is" msgstr "我的用户名是" diff --git a/news/4656.bugfix b/news/4656.bugfix new file mode 100644 index 0000000000..06b9558589 --- /dev/null +++ b/news/4656.bugfix @@ -0,0 +1 @@ +Fixed wrong localization on password reset page @iRohitSingh \ No newline at end of file diff --git a/src/components/theme/PasswordReset/PasswordReset.jsx b/src/components/theme/PasswordReset/PasswordReset.jsx index ca822ed4b2..f676b2d3d0 100644 --- a/src/components/theme/PasswordReset/PasswordReset.jsx +++ b/src/components/theme/PasswordReset/PasswordReset.jsx @@ -31,7 +31,7 @@ const messages = defineMessages({ }, usernameTitle: { id: 'My username is', - defaultMessage: 'My username is', + defaultMessage: 'My user name is', }, emailTitle: { id: 'My email is', diff --git a/src/components/theme/PasswordReset/RequestPasswordReset.jsx b/src/components/theme/PasswordReset/RequestPasswordReset.jsx index 3154a95321..db429f94f3 100644 --- a/src/components/theme/PasswordReset/RequestPasswordReset.jsx +++ b/src/components/theme/PasswordReset/RequestPasswordReset.jsx @@ -32,7 +32,7 @@ const messages = defineMessages({ }, usernameTitle: { id: 'label_my_username_is', - defaultMessage: 'My username is', + defaultMessage: 'My user name is', }, emailTitle: { id: 'label_my_email_is', diff --git a/src/components/theme/PasswordReset/__snapshots__/RequestPasswordReset.test.jsx.snap b/src/components/theme/PasswordReset/__snapshots__/RequestPasswordReset.test.jsx.snap index 027cf7b048..0eaf6db99a 100644 --- a/src/components/theme/PasswordReset/__snapshots__/RequestPasswordReset.test.jsx.snap +++ b/src/components/theme/PasswordReset/__snapshots__/RequestPasswordReset.test.jsx.snap @@ -47,7 +47,7 @@ exports[`RequestPasswordReset renders a RequestPasswordReset component 1`] = ` className="mocked-default-widget" id="mocked-field-username" > - My username is + My user name is - No description
    From c40e772701e4f6771a541898621457ec45500859 Mon Sep 17 00:00:00 2001 From: Bittor Poza Date: Wed, 5 Apr 2023 17:56:48 +0200 Subject: [PATCH 320/326] Add current page parameter to the route in the listing and search block pagination (#4159) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikel Larreategi Co-authored-by: Víctor Fernández de Alba Co-authored-by: ionlizarazu --- news/4159.bugfix | 1 + .../Blocks/Listing/ListingBody.test.jsx | 20 +++ .../Blocks/Listing/withQuerystringResults.jsx | 3 +- .../manage/Blocks/Search/hocs/withSearch.jsx | 12 +- src/helpers/Utils/usePagination.js | 62 +++++++--- src/helpers/Utils/usePagination.test.js | 115 ++++++++++++++++++ 6 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 news/4159.bugfix create mode 100644 src/helpers/Utils/usePagination.test.js diff --git a/news/4159.bugfix b/news/4159.bugfix new file mode 100644 index 0000000000..d9b9f18dfa --- /dev/null +++ b/news/4159.bugfix @@ -0,0 +1 @@ +Added current page parameter to route in listing and search block pagination - Fix: #3868 @bipoza \ No newline at end of file diff --git a/src/components/manage/Blocks/Listing/ListingBody.test.jsx b/src/components/manage/Blocks/Listing/ListingBody.test.jsx index d52c7df814..8a5c359972 100644 --- a/src/components/manage/Blocks/Listing/ListingBody.test.jsx +++ b/src/components/manage/Blocks/Listing/ListingBody.test.jsx @@ -36,6 +36,26 @@ test('renders a ListingBody component', () => { content: { data: { is_folderish: true, + blocks: { + '839ee00b-013b-4f4a-9b10-8867938fdac3': { + '@type': 'listing', + block: '839ee00b-013b-4f4a-9b10-8867938fdac3', + headlineTag: 'h2', + query: [], + querystring: { + b_size: '2', + query: [ + { + i: 'path', + o: 'plone.app.querystring.operation.string.absolutePath', + v: '/', + }, + ], + sort_order: 'ascending', + }, + variation: 'default', + }, + }, }, }, intl: { diff --git a/src/components/manage/Blocks/Listing/withQuerystringResults.jsx b/src/components/manage/Blocks/Listing/withQuerystringResults.jsx index d675fc050a..c550cf93cb 100644 --- a/src/components/manage/Blocks/Listing/withQuerystringResults.jsx +++ b/src/components/manage/Blocks/Listing/withQuerystringResults.jsx @@ -25,7 +25,7 @@ export default function withQuerystringResults(WrappedComponent) { const [initialPath] = React.useState(getBaseUrl(path)); const copyFields = ['limit', 'query', 'sort_on', 'sort_order', 'depth']; - + const { currentPage, setCurrentPage } = usePagination(data.block, 1); const adaptedQuery = Object.assign( variation?.fullobjects ? { fullobjects: 1 } : { metadata_fields: '_all' }, { @@ -37,7 +37,6 @@ export default function withQuerystringResults(WrappedComponent) { : {}, ), ); - const { currentPage, setCurrentPage } = usePagination(querystring, 1); const querystringResults = useSelector( (state) => state.querystringsearch.subrequests, ); diff --git a/src/components/manage/Blocks/Search/hocs/withSearch.jsx b/src/components/manage/Blocks/Search/hocs/withSearch.jsx index 5177dd6304..fd1842fbc1 100644 --- a/src/components/manage/Blocks/Search/hocs/withSearch.jsx +++ b/src/components/manage/Blocks/Search/hocs/withSearch.jsx @@ -144,12 +144,16 @@ const getSearchFields = (searchData) => { }; /** - * A HOC that will mirror the search block state to a hash location + * A hook that will mirror the search block state to a hash location */ const useHashState = () => { const location = useLocation(); const history = useHistory(); + /** + * Required to maintain parameter compatibility. + With this we will maintain support for receiving hash (#) and search (?) type parameters. + */ const oldState = React.useMemo(() => { return { ...qs.parse(location.search), @@ -165,7 +169,7 @@ const useHashState = () => { const setSearchData = React.useCallback( (searchData) => { - const newParams = qs.parse(location.hash); + const newParams = qs.parse(location.search); let changed = false; @@ -182,11 +186,11 @@ const useHashState = () => { if (changed) { history.push({ - hash: qs.stringify(newParams), + search: qs.stringify(newParams), }); } }, - [history, oldState, location.hash], + [history, oldState, location.search], ); return [current, setSearchData]; diff --git a/src/helpers/Utils/usePagination.js b/src/helpers/Utils/usePagination.js index acb12d2ac5..dc984e9932 100644 --- a/src/helpers/Utils/usePagination.js +++ b/src/helpers/Utils/usePagination.js @@ -1,25 +1,59 @@ -import React from 'react'; -import { isEqual } from 'lodash'; -import { usePrevious } from './usePrevious'; -import useDeepCompareEffect from 'use-deep-compare-effect'; +import React, { useRef, useEffect } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import qs from 'query-string'; +import { useSelector } from 'react-redux'; +import { slugify } from '@plone/volto/helpers/Utils/Utils'; + +/** + * @function useCreatePageQueryStringKey + * @description A hook that creates a key with an id if there are multiple blocks with pagination. + * @returns {string} Example: page || page_012345678 + */ +const useCreatePageQueryStringKey = (id) => { + const blockTypesWithPagination = ['search', 'listing']; + const blocks = useSelector((state) => state?.content?.data?.blocks) || []; + const blocksLayout = + useSelector((state) => state?.content?.data?.blocks_layout?.items) || []; + const displayedBlocks = blocksLayout?.map((item) => blocks[item]); + const hasMultiplePaginations = + displayedBlocks.filter((item) => + blockTypesWithPagination.includes(item['@type']), + ).length > 1 || false; + + return hasMultiplePaginations ? slugify(`page-${id}`) : 'page'; +}; /** * A pagination helper that tracks the query and resets pagination in case the * query changes. */ -export const usePagination = (query, defaultPage = 1) => { - const previousQuery = usePrevious(query); - const [currentPage, setCurrentPage] = React.useState(defaultPage); +export const usePagination = (id = null, defaultPage = 1) => { + const location = useLocation(); + const history = useHistory(); + const pageQueryStringKey = useCreatePageQueryStringKey(id); + const pageQueryParam = + qs.parse(location.search)[pageQueryStringKey] || defaultPage; + const [currentPage, setCurrentPage] = React.useState( + parseInt(pageQueryParam), + ); + const queryRef = useRef(qs.parse(location.search)?.query); - useDeepCompareEffect(() => { - setCurrentPage(defaultPage); - }, [query, previousQuery, defaultPage]); + useEffect(() => { + if (queryRef.current !== qs.parse(location.search)?.query) { + setCurrentPage(defaultPage); + queryRef.current = qs.parse(location.search)?.query; + } + const newParams = { + ...qs.parse(location.search), + [pageQueryStringKey]: currentPage, + }; + history.replace({ + search: qs.stringify(newParams), + }); + }, [currentPage, defaultPage, location.search, history, pageQueryStringKey]); return { - currentPage: - previousQuery && !isEqual(previousQuery, query) - ? defaultPage - : currentPage, + currentPage, setCurrentPage, }; }; diff --git a/src/helpers/Utils/usePagination.test.js b/src/helpers/Utils/usePagination.test.js new file mode 100644 index 0000000000..26924d78a0 --- /dev/null +++ b/src/helpers/Utils/usePagination.test.js @@ -0,0 +1,115 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { usePagination } from './usePagination'; +import * as redux from 'react-redux'; +import routeData from 'react-router'; +import { slugify } from '@plone/volto/helpers/Utils/Utils'; + +const searchBlockId = '545b33de-92cf-4747-969d-68851837b317'; +const searchBlockId2 = '454b33de-92cf-4747-969d-68851837b713'; +const searchBlock = { + '@type': 'search', + query: { + b_size: '4', + query: [ + { + i: 'path', + o: 'plone.app.querystring.operation.string.relativePath', + v: '', + }, + ], + sort_order: 'ascending', + }, + showSearchInput: true, + showTotalResults: true, +}; +let state = { + content: { + data: { + blocks: { + [searchBlockId]: searchBlock, + }, + blocks_layout: { + items: [searchBlockId], + }, + }, + }, +}; + +let mockUseLocationValue = { + pathname: '/testroute', + search: '', +}; + +const setUp = (searchParam, numberOfSearches) => { + mockUseLocationValue.search = searchParam; + if (numberOfSearches > 1) { + state.content.data.blocks[searchBlockId2] = searchBlock; + state.content.data.blocks_layout.items.push(searchBlockId2); + } + return renderHook(({ id, defaultPage }) => usePagination(id, defaultPage), { + initialProps: { + id: searchBlockId, + defaultPage: 1, + }, + }); +}; + +describe(`Tests for usePagination, for the block ${searchBlockId}`, () => { + const useLocation = jest.spyOn(routeData, 'useLocation'); + const useHistory = jest.spyOn(routeData, 'useHistory'); + const useSelector = jest.spyOn(redux, 'useSelector'); + beforeEach(() => { + useLocation.mockReturnValue(mockUseLocationValue); + useHistory.mockReturnValue({ replace: jest.fn() }); + useSelector.mockImplementation((cb) => cb(state)); + }); + + it('1 paginated block with id and defaultPage 1 - shoud be 1', () => { + const { result } = setUp(); + expect(result.current.currentPage).toBe(1); + }); + + it('1 paginated block without params - shoud be 1', () => { + const { result } = setUp(); + expect(result.current.currentPage).toBe(1); + }); + + const param1 = '?page=2'; + it(`1 paginated block with params: ${param1} - shoud be 2`, () => { + const { result } = setUp(param1); + expect(result.current.currentPage).toBe(2); + }); + + const param2 = `?${slugify(`page-${searchBlockId}`)}=2`; + it(`2 paginated blocks with current block in the params: ${param2} - shoud be 2`, () => { + const { result } = setUp(param2, 2); + expect(result.current.currentPage).toBe(2); + }); + + const param3 = `?${slugify(`page-${searchBlockId2}`)}=2`; + it(`2 paginated blocks with the other block in the params: ${param3} - shoud be 1`, () => { + const { result } = setUp(param3, 2); + expect(result.current.currentPage).toBe(1); + }); + + const param4 = `?${slugify(`page-${searchBlockId}`)}=2&${slugify( + `page-${searchBlockId2}`, + )}=1`; + it(`2 paginated blocks with both blocks in the params, current 2: ${param4} - shoud be 2`, () => { + const { result } = setUp(param4, 2); + expect(result.current.currentPage).toBe(2); + }); + + const param5 = `?${slugify(`page-${searchBlockId}`)}=1&${slugify( + `page-${searchBlockId2}`, + )}=2`; + it(`2 paginated blocks with both blocks in the params, current 1: ${param5} - shoud be 1`, () => { + const { result } = setUp(param5, 2); + expect(result.current.currentPage).toBe(1); + }); + + it(`2 paginated blocks with wrong page param: ${param1} - shoud be 1`, () => { + const { result } = setUp(param1, 2); + expect(result.current.currentPage).toBe(1); + }); +}); From 9d767d43348a3d27633c5000bc362932e8016508 Mon Sep 17 00:00:00 2001 From: Rob Gietema Date: Thu, 6 Apr 2023 09:35:06 +0200 Subject: [PATCH 321/326] Added querystring search get option (#4658) --- news/4658.feature | 1 + .../querystringsearch/querystringsearch.js | 34 +++++++++++-------- src/config/index.js | 1 + 3 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 news/4658.feature diff --git a/news/4658.feature b/news/4658.feature new file mode 100644 index 0000000000..0c4c3cc11b --- /dev/null +++ b/news/4658.feature @@ -0,0 +1 @@ +Added querystring search get option. @robgietema \ No newline at end of file diff --git a/src/actions/querystringsearch/querystringsearch.js b/src/actions/querystringsearch/querystringsearch.js index 9dff75b72d..6b178436f7 100644 --- a/src/actions/querystringsearch/querystringsearch.js +++ b/src/actions/querystringsearch/querystringsearch.js @@ -31,24 +31,30 @@ export function getQueryStringResults(path, data, subrequest, page) { } } + const query = { + ...requestData, + ...(!requestData.b_size && { + b_size: settings.defaultPageSize, + }), + ...(page && { + b_start: requestData.b_size + ? data.b_size * (page - 1) + : settings.defaultPageSize * (page - 1), + }), + query: requestData?.query, + }; + return { type: GET_QUERYSTRING_RESULTS, subrequest, request: { - op: 'post', - path: `${path}/@querystring-search`, - data: { - ...requestData, - ...(!requestData.b_size && { - b_size: settings.defaultPageSize, - }), - ...(page && { - b_start: requestData.b_size - ? data.b_size * (page - 1) - : settings.defaultPageSize * (page - 1), - }), - query: requestData?.query, - }, + op: settings.querystringSearchGet ? 'get' : 'post', + path: `${path}/@querystring-search${ + settings.querystringSearchGet + ? `?query=${encodeURIComponent(JSON.stringify(query))}` + : '' + }`, + data: settings.querystringSearchGet ? null : query, }, }; } diff --git a/src/config/index.js b/src/config/index.js index 60985f5e26..6833ff56b8 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -180,6 +180,7 @@ let config = { styleClassNameConverters, hashLinkSmoothScroll: false, styleClassNameExtenders, + querystringSearchGet: false, }, experimental: { addBlockButton: { From 90363c2ff7dba330fdc705bf95195b2b848acff3 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 7 Apr 2023 11:12:21 +0200 Subject: [PATCH 322/326] Changelog --- packages/scripts/news/4316.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/scripts/news/4316.bugfix diff --git a/packages/scripts/news/4316.bugfix b/packages/scripts/news/4316.bugfix new file mode 100644 index 0000000000..0e012ad4f1 --- /dev/null +++ b/packages/scripts/news/4316.bugfix @@ -0,0 +1 @@ +Fixed i18n script to avoid overwriting translations with an empty msgstr @danalvrz From d8b665f387c2827757782123083a9918394c7d12 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 7 Apr 2023 11:12:44 +0200 Subject: [PATCH 323/326] Release @plone/scripts 3.0.0 --- packages/scripts/CHANGELOG.md | 11 +++++++++++ packages/scripts/news/4316.bugfix | 1 - packages/scripts/news/4546.breaking | 1 - packages/scripts/package.json | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) delete mode 100644 packages/scripts/news/4316.bugfix delete mode 100644 packages/scripts/news/4546.breaking diff --git a/packages/scripts/CHANGELOG.md b/packages/scripts/CHANGELOG.md index 560a57ee9b..84c99079d7 100644 --- a/packages/scripts/CHANGELOG.md +++ b/packages/scripts/CHANGELOG.md @@ -8,6 +8,17 @@ +## 3.0.0 (2023-04-07) + +### Breaking + +- Remove dependency on `simple-git`. It is used by `mrs-developer` but not directly. @davisagli [#4546](https://github.com/plone/volto/issues/4546) + +### Bugfix + +- Fixed i18n script to avoid overwriting translations with an empty msgstr @danalvrz [#4316](https://github.com/plone/volto/issues/4316) + + ## 2.3.0 (2023-01-13) ### Feature diff --git a/packages/scripts/news/4316.bugfix b/packages/scripts/news/4316.bugfix deleted file mode 100644 index 0e012ad4f1..0000000000 --- a/packages/scripts/news/4316.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed i18n script to avoid overwriting translations with an empty msgstr @danalvrz diff --git a/packages/scripts/news/4546.breaking b/packages/scripts/news/4546.breaking deleted file mode 100644 index 468d189887..0000000000 --- a/packages/scripts/news/4546.breaking +++ /dev/null @@ -1 +0,0 @@ -Remove dependency on `simple-git`. It is used by `mrs-developer` but not directly. @davisagli diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 2936321d4f..6d8ed40cf6 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -9,7 +9,7 @@ } ], "license": "MIT", - "version": "2.3.0", + "version": "3.0.0", "repository": { "type": "git", "url": "git@github.com:plone/volto.git" From 00029a2cb9fd9599fd1a1707aa5018a96d1e6873 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sat, 8 Apr 2023 15:15:00 -0700 Subject: [PATCH 324/326] =?UTF-8?q?Update=20Volto=20contributing=20to=20al?= =?UTF-8?q?ign=20with=20and=20refer=20to=20the=20new=20Plone=20co=E2=80=A6?= =?UTF-8?q?=20(#4634)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../developer-guidelines/contributing.md | 81 ++++++++++--------- news/4634.documentation | 1 + 2 files changed, 42 insertions(+), 40 deletions(-) create mode 100644 news/4634.documentation diff --git a/docs/source/developer-guidelines/contributing.md b/docs/source/developer-guidelines/contributing.md index 04446bf2db..b2b7a445b6 100644 --- a/docs/source/developer-guidelines/contributing.md +++ b/docs/source/developer-guidelines/contributing.md @@ -7,11 +7,15 @@ myst: "keywords": "Volto, Plone, frontend, React, guidelines" --- +(contributing-to-volto-label)= + # Contributing to Volto -You may have an issue to report, make a feature request, report a security vulnerability, or you want to create a pull request. -You have come to the right place to learn how to do so. +First read {doc}`plone:contributing/index`. +Volto follows those guidelines with a few specific variations, as described in this chapter. + +(contributing-reporting-an-issue-or-making-a-feature-request-label)= ## Reporting an issue or making a feature request @@ -22,65 +26,60 @@ When in doubt, create one in the [CMFPlone issue tracker](https://github.com/plo In your report, please specify a few things: -- What are the steps to reproduce the problem? -- What do you expect when you follow those steps? -- What do you observe? -- Which Plone version are you using? -- Include relevant screenshots, error messages, and stack traces. +- What are the steps to reproduce the problem? +- What do you expect when you follow those steps? +- What do you observe? +- Which Plone version are you using? +- Include relevant screenshots, error messages, and stack traces. + + +(contributing-sign-and-return-the-plone-contributor-agreement-label)= + +## Sign and return the Plone Contributor Agreement + +The Volto Team reviews pull requests only from people with a GitHub account who have signed and returned the {ref}`Plone Contributor Agreement `, and subsequently been assigned to a Plone Team in GitHub. + + +(contributing-branch-policy-label)= ## Branch policy ```{include} ./branch-policy.md ``` -## Sign and send us the Plone Contributors Agreement -You must sign the [Plone Contributor Agreement](https://plone.org/foundation/contributors-agreement) to contribute code and documentation to any Plone project. -This means that we can NOT accept pull requests from you from any location until you do this. +(contributing-translations-label)= +## Translations -## First Time Contributors: work from a fork +All text that can be shown in a browser must be translatable. +Please mark all such strings as translatable as defined in the [i18n guide](../recipes/i18n.md). -When we have received the Plone Contributor Agreement and it has been process by a community member, you will be added to the Plone organisation and added to the Contributors Team on GitHub. -You can create issues, add comments to existing issues, and discuss the development of Plone with others. -Creating branches and editing code on our main repositories is restricted and granted to members who have been active in the Plone community for a while and have shown continued interest to contributing. -- First, verify that your issue is valid by creating it and discussing it with other developers. -- Then, create a fork of the repo in your own workspace, work on your code and make commits. -- When you want to run our code quality checks, create a Pull Request from your repo/branch to the main repository. -- As a security measure, the first run of code Quality checks need to be [approved by a member in our Developers Team](https://docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks) -- A Developer with full access to the workflow will need to review and approve your Pull request. +(contributing-change-log-entry-label)= -Please be aware that as long as you are working from a fork, you are the only developer being able to commit to your branch. -Collaboration between developers on bigger pull requests is more efficient when these are done on branch on the main repo. -Therefore: as your first time contribution don't choose a too big problem to solve. -Pick something that you can fix or complete from start to end. +## Change log entry +Volto requires that you include a change log entry or news item with your contribution. +Your attribution must be in the format of `@github_username`. +```{seealso} +For details see {ref}`contributing-change-log-label`. +``` -## Documenting your changes -All pull requests must include a `towncrier` news item. -This is a file that is placed in the root of the repository directory at `/news`. -Its format must be `###.type`, where `###` is the referenced GitHub issue or pull request number, `.` is the literal extension delimiter, and `type` is one of the following strings. +(contributing-documenting-your-changes-label)= -- `breaking` for breaking changes -- `bugfix` for bug fixes -- `documentation` for documentation -- `feature` for new features -- `internal` for internal changes +## Documenting your changes If the feature includes a breaking change, you must include instructions for how to upgrade in the [upgrade guide](../upgrade-guide/index.md). -All text that can be shown in a browser must be translatable. Please mark all such -strings as translatable as defined in the [i18n guide](../recipes/i18n.md). +(contributing-code-quality-label)= -## Code Quality +## Code quality All pull requests must pass tests, documentation builds, and other code quality checks. -Developers are strongly encouraged to run these checks locally before creating a pull request. -Contributors without full Developer access will need to create a Pull request first and get approval for their first PR from a Developer. These checks are enforced automatically on every pull request, so you might as well save time and frustration by doing these checks locally first. Specifically: @@ -90,7 +89,9 @@ Specifically: - {doc}`./acceptance-tests` -If after reading this you become hesitant, don't worry. -You can always create a pull request, mark it as "Draft", and improve the above points later, requesting help from the community. +(contributing-final-advice-label)= + +## Final advice -Welcome to the Plone community, and thank you for contributing! +If you become hesitant after reading the foregoing, don't worry. +You can always create a pull request, mark it as "Draft", and improve these points later while requesting help from the community. diff --git a/news/4634.documentation b/news/4634.documentation new file mode 100644 index 0000000000..f3498b18ff --- /dev/null +++ b/news/4634.documentation @@ -0,0 +1 @@ +Update Volto contributing to align with and refer to the new Plone core code contributing requirements. @stevepiercy From a9b96f0399fba4d89a70edb4accea91af3f81d4d Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 9 Apr 2023 03:51:40 -0700 Subject: [PATCH 325/326] Move developer guidelines to contributing #4665 (#4666) --- CHANGELOG.md | 2 +- docs/source/cheatsheet.md | 2 +- docs/source/conf.py | 2 +- .../Quanta.pdf | Bin .../acceptance-tests.md | 0 .../accessibility-guidelines.md | 0 .../branch-policy.md | 0 .../design-principles.md | 0 .../icons.md | 0 .../contributing.md => contributing/index.md} | 44 +++++++++++++++--- .../language-features.md | 0 .../linting.md | 0 .../react.md | 0 .../redux.md | 0 .../routing.md | 0 .../style-guide.md | 0 .../testing.md | 0 .../typescript.md | 0 .../volto-core-addons.md | 0 docs/source/developer-guidelines/index.md | 38 --------------- docs/source/index.md | 2 +- news/4666.documentation | 1 + 22 files changed, 43 insertions(+), 48 deletions(-) rename docs/source/{developer-guidelines => contributing}/Quanta.pdf (100%) rename docs/source/{developer-guidelines => contributing}/acceptance-tests.md (100%) rename docs/source/{developer-guidelines => contributing}/accessibility-guidelines.md (100%) rename docs/source/{developer-guidelines => contributing}/branch-policy.md (100%) rename docs/source/{developer-guidelines => contributing}/design-principles.md (100%) rename docs/source/{developer-guidelines => contributing}/icons.md (100%) rename docs/source/{developer-guidelines/contributing.md => contributing/index.md} (65%) rename docs/source/{developer-guidelines => contributing}/language-features.md (100%) rename docs/source/{developer-guidelines => contributing}/linting.md (100%) rename docs/source/{developer-guidelines => contributing}/react.md (100%) rename docs/source/{developer-guidelines => contributing}/redux.md (100%) rename docs/source/{developer-guidelines => contributing}/routing.md (100%) rename docs/source/{developer-guidelines => contributing}/style-guide.md (100%) rename docs/source/{developer-guidelines => contributing}/testing.md (100%) rename docs/source/{developer-guidelines => contributing}/typescript.md (100%) rename docs/source/{developer-guidelines => contributing}/volto-core-addons.md (100%) delete mode 100644 docs/source/developer-guidelines/index.md create mode 100644 news/4666.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index c492f428b4..a24ff4bb09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ - Deleted duplicate import and fixed training URLs. @yahya-cloud [#4523](https://github.com/plone/volto/issues/4523) - Fix grammar in PR #4542. @stevepiercy [#4555](https://github.com/plone/volto/issues/4555) -- Fix broken links at ReactJS.org. @stevepiercy [#4569](https://github.com/plone/volto/issues/4569) +- Fix broken links at `ReactJS.org`. @stevepiercy [#4569](https://github.com/plone/volto/issues/4569) - Fix video warnings and link errors. @stevepiercy [#4578](https://github.com/plone/volto/issues/4578) diff --git a/docs/source/cheatsheet.md b/docs/source/cheatsheet.md index dc7eb80d88..f7b8601d7d 100644 --- a/docs/source/cheatsheet.md +++ b/docs/source/cheatsheet.md @@ -14,7 +14,7 @@ myst: # Cheatsheet ```{seealso} -{doc}`plone:contributing/myst-reference` +{doc}`plone:contributing/documentation/myst-reference` ``` diff --git a/docs/source/conf.py b/docs/source/conf.py index 1421636d3f..679833e9a9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -119,7 +119,7 @@ "spelling_wordlist.txt", "**/CHANGES.rst", "**/LICENSE.rst", - "developer-guidelines/branch-policy.md", + "contributing/branch-policy.md", ] html_extra_path = [ diff --git a/docs/source/developer-guidelines/Quanta.pdf b/docs/source/contributing/Quanta.pdf similarity index 100% rename from docs/source/developer-guidelines/Quanta.pdf rename to docs/source/contributing/Quanta.pdf diff --git a/docs/source/developer-guidelines/acceptance-tests.md b/docs/source/contributing/acceptance-tests.md similarity index 100% rename from docs/source/developer-guidelines/acceptance-tests.md rename to docs/source/contributing/acceptance-tests.md diff --git a/docs/source/developer-guidelines/accessibility-guidelines.md b/docs/source/contributing/accessibility-guidelines.md similarity index 100% rename from docs/source/developer-guidelines/accessibility-guidelines.md rename to docs/source/contributing/accessibility-guidelines.md diff --git a/docs/source/developer-guidelines/branch-policy.md b/docs/source/contributing/branch-policy.md similarity index 100% rename from docs/source/developer-guidelines/branch-policy.md rename to docs/source/contributing/branch-policy.md diff --git a/docs/source/developer-guidelines/design-principles.md b/docs/source/contributing/design-principles.md similarity index 100% rename from docs/source/developer-guidelines/design-principles.md rename to docs/source/contributing/design-principles.md diff --git a/docs/source/developer-guidelines/icons.md b/docs/source/contributing/icons.md similarity index 100% rename from docs/source/developer-guidelines/icons.md rename to docs/source/contributing/icons.md diff --git a/docs/source/developer-guidelines/contributing.md b/docs/source/contributing/index.md similarity index 65% rename from docs/source/developer-guidelines/contributing.md rename to docs/source/contributing/index.md index b2b7a445b6..adc5e4d2aa 100644 --- a/docs/source/developer-guidelines/contributing.md +++ b/docs/source/contributing/index.md @@ -1,10 +1,10 @@ --- myst: html_meta: - "description": "A guide on how to contribute to Volto, the frontend for Plone." - "property=og:description": "A guide on how to contribute to Volto, the frontend for Plone." - "property=og:title": "Contributing to Volto" - "keywords": "Volto, Plone, frontend, React, guidelines" + "description": "How to contribute to Volto, the frontend for Plone." + "property=og:description": "How to contribute to Volto, the frontend for Plone." + "property=og:title": "How to contribute to Volto, the frontend for Plone." + "keywords": "Plone, Volto, contributing, developer, guidelines" --- (contributing-to-volto-label)= @@ -33,11 +33,11 @@ In your report, please specify a few things: - Include relevant screenshots, error messages, and stack traces. -(contributing-sign-and-return-the-plone-contributor-agreement-label)= +(contributing-volto-sign-and-return-the-plone-contributor-agreement-label)= ## Sign and return the Plone Contributor Agreement -The Volto Team reviews pull requests only from people with a GitHub account who have signed and returned the {ref}`Plone Contributor Agreement `, and subsequently been assigned to a Plone Team in GitHub. +The Volto Team reviews pull requests only from people with a GitHub account who have signed and returned the {ref}`Plone Contributor Agreement `, and subsequently been assigned to a Plone Team in GitHub. (contributing-branch-policy-label)= @@ -89,6 +89,38 @@ Specifically: - {doc}`./acceptance-tests` +(contributing-developer-guidelines-label)= + +## Developer guidelines + +Development and configuration of Volto is managed through your {ref}`choice of Plone installation method `. +You may choose to install Plone via {ref}`containers ` or from its {ref}`packages `. + +```{todo} +When referring to installation and configuration of Plone's backend, this part of the Volto documentation may have obsolete content. +The most current information for installing and configuring Plone is in {ref}`install-index-label`. +Please report any issues in the [Volto issue tracker](https://github.com/plone/volto/issues/). +``` + +```{toctree} +:maxdepth: 1 + +design-principles +style-guide +language-features +linting +react +redux +routing +icons +testing +acceptance-tests +accessibility-guidelines +typescript +volto-core-addons +``` + + (contributing-final-advice-label)= ## Final advice diff --git a/docs/source/developer-guidelines/language-features.md b/docs/source/contributing/language-features.md similarity index 100% rename from docs/source/developer-guidelines/language-features.md rename to docs/source/contributing/language-features.md diff --git a/docs/source/developer-guidelines/linting.md b/docs/source/contributing/linting.md similarity index 100% rename from docs/source/developer-guidelines/linting.md rename to docs/source/contributing/linting.md diff --git a/docs/source/developer-guidelines/react.md b/docs/source/contributing/react.md similarity index 100% rename from docs/source/developer-guidelines/react.md rename to docs/source/contributing/react.md diff --git a/docs/source/developer-guidelines/redux.md b/docs/source/contributing/redux.md similarity index 100% rename from docs/source/developer-guidelines/redux.md rename to docs/source/contributing/redux.md diff --git a/docs/source/developer-guidelines/routing.md b/docs/source/contributing/routing.md similarity index 100% rename from docs/source/developer-guidelines/routing.md rename to docs/source/contributing/routing.md diff --git a/docs/source/developer-guidelines/style-guide.md b/docs/source/contributing/style-guide.md similarity index 100% rename from docs/source/developer-guidelines/style-guide.md rename to docs/source/contributing/style-guide.md diff --git a/docs/source/developer-guidelines/testing.md b/docs/source/contributing/testing.md similarity index 100% rename from docs/source/developer-guidelines/testing.md rename to docs/source/contributing/testing.md diff --git a/docs/source/developer-guidelines/typescript.md b/docs/source/contributing/typescript.md similarity index 100% rename from docs/source/developer-guidelines/typescript.md rename to docs/source/contributing/typescript.md diff --git a/docs/source/developer-guidelines/volto-core-addons.md b/docs/source/contributing/volto-core-addons.md similarity index 100% rename from docs/source/developer-guidelines/volto-core-addons.md rename to docs/source/contributing/volto-core-addons.md diff --git a/docs/source/developer-guidelines/index.md b/docs/source/developer-guidelines/index.md deleted file mode 100644 index a96f92b02b..0000000000 --- a/docs/source/developer-guidelines/index.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -myst: - html_meta: - "description": "Developer Guidelines" - "property=og:description": "Developer Guidelines" - "property=og:title": "Developer Guidelines" - "keywords": "Developer, Guidelines" ---- - -# Developer guidelines - -Development and configuration of Volto is managed through your {ref}`choice of Plone installation method `. -You may choose to install Plone via {ref}`containers ` or from its {ref}`packages `. - -```{todo} -When referring to installation and configuration of Plone's backend, this part of the Volto documentation may have obsolete content. -The most current information for installing and configuring Plone is in {ref}`install-index-label`. -Please report any issues in the [Volto issue tracker](https://github.com/plone/volto/issues/). -``` - -```{toctree} -:maxdepth: 1 - -contributing -design-principles -style-guide -language-features -linting -react -redux -routing -icons -testing -acceptance-tests -accessibility-guidelines -typescript -volto-core-addons -``` diff --git a/docs/source/index.md b/docs/source/index.md index 222455dae2..ee7723015f 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -47,7 +47,7 @@ addons/index backend/index deploying/index upgrade-guide/index -developer-guidelines/index user-manual/index +contributing/index release-notes/index ``` diff --git a/news/4666.documentation b/news/4666.documentation new file mode 100644 index 0000000000..0ad620aa45 --- /dev/null +++ b/news/4666.documentation @@ -0,0 +1 @@ +Rename "Developer Guidelines" to "Contributing". @stevepiercy From c09c578e1c8f5fd3c33b8562a75c262485e6b099 Mon Sep 17 00:00:00 2001 From: Steve Piercy Date: Sun, 9 Apr 2023 15:48:01 -0700 Subject: [PATCH 326/326] Make URL a literal string to fix broken link (#4667) --- CHANGELOG.md | 2 +- news/4667.documentation | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 news/4667.documentation diff --git a/CHANGELOG.md b/CHANGELOG.md index a24ff4bb09..f47070e07f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,7 +143,7 @@ ### Documentation -- Fix broken links at ReactJS.org. @stevepiercy [#4569](https://github.com/plone/volto/issues/4569) +- Fix broken links at `ReactJS.org`. @stevepiercy [#4569](https://github.com/plone/volto/issues/4569) - Fix video warnings and link errors. @stevepiercy [#4578](https://github.com/plone/volto/issues/4578) diff --git a/news/4667.documentation b/news/4667.documentation new file mode 100644 index 0000000000..6ccff67f8b --- /dev/null +++ b/news/4667.documentation @@ -0,0 +1 @@ +Fix broken link to `ReactJS.org`. @stevepiercy