diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 1dbf2816..08d8ed83 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -2,7 +2,8 @@ name: Acceptance tests on: [push, pull_request] env: - node-version: 20.x + NODE_VERSION: 20.x + CYPRESS_RETRIES: 2 jobs: acceptance: @@ -11,18 +12,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Use Node.js ${{ env.node-version }} + - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: ${{ env.node-version }} + node-version: ${{ env.NODE_VERSION }} - - uses: pnpm/action-setup@v3 - name: Install pnpm - with: - version: 8 - # We don't want to install until later, - # when the cache and Cypress are in place - run_install: false + - name: Enable corepack + run: corepack enable - name: Get pnpm store directory shell: bash @@ -42,7 +38,7 @@ jobs: uses: actions/cache@v4 with: path: ~/.cache/Cypress - key: binary-${{ env.node-version }}-${{ hashFiles('pnpm-lock.yaml') }} + key: binary-${{ env.NODE_VERSION }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Install dependencies run: make install @@ -56,8 +52,8 @@ jobs: name: Start Servers with: run: | - make start-test-acceptance-server-ci & - make start-test-acceptance-frontend & + make ci-acceptance-backend-start & + make acceptance-frontend-prod-start & # your step-level and job-level environment variables are available to your commands as-is # npm install will count towards the wait-for timeout # whenever possible, move unrelated scripts to a different step @@ -84,7 +80,7 @@ jobs: # working-directory: backend - - run: make test-acceptance-headless + - run: make ci-acceptance-test # Upload Cypress screenshots - uses: actions/upload-artifact@v3 @@ -106,18 +102,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Use Node.js 20.x + - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: ${{ env.NODE_VERSION }} - - uses: pnpm/action-setup@v3 - name: Install pnpm - with: - version: 8 - # We don't want to install until later, - # when the cache and Cypress are in place - run_install: false + - name: Enable corepack + run: corepack enable - name: Get pnpm store directory shell: bash @@ -151,8 +142,8 @@ jobs: name: Start Servers with: run: | - make start-test-acceptance-server-a11y-ci & - make start-test-acceptance-frontend-a11y & + make ci-acceptance-a11y-backend-start & + make acceptance-a11y-frontend-prod-start & # your step-level and job-level environment variables are available to your commands as-is # npm install will count towards the wait-for timeout # whenever possible, move unrelated scripts to a different step @@ -179,7 +170,7 @@ jobs: # working-directory: backend - - run: make test-acceptance-headless-a11y + - run: make ci-acceptance-a11y-test # Upload Cypress screenshots - uses: actions/upload-artifact@v3 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index a7b25276..22bee9ae 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -6,7 +6,7 @@ on: - main env: - node-version: 20.x + NODE_VERSION: 20.x ADDON_NAME: volto-light-theme jobs: @@ -21,10 +21,10 @@ jobs: - name: Install pipx run: pip install towncrier - - name: Use Node.js ${{ env.node-version }} + - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: ${{ env.node-version }} + node-version: ${{ env.NODE_VERSION }} - uses: pnpm/action-setup@v3 name: Install pnpm diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml index 2b2a7c52..8f6843bd 100644 --- a/.github/workflows/code.yml +++ b/.github/workflows/code.yml @@ -2,7 +2,7 @@ name: Code analysis checks on: [push] env: - node-version: 20.x + NODE_VERSION: 20.x jobs: codeanalysis: @@ -12,18 +12,13 @@ jobs: - name: Main checkout uses: actions/checkout@v4 - - name: Use Node.js ${{ env.node-version }} + - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: ${{ env.node-version }} + node-version: ${{ env.NODE_VERSION }} - - uses: pnpm/action-setup@v3 - name: Install pnpm - with: - version: 8 - # We don't want to install until later, - # when the cache and Cypress are in place - run_install: false + - name: Enable corepack + run: corepack enable - name: Get pnpm store directory shell: bash diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 025ec6cb..dfecc81f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -29,7 +29,7 @@ jobs: ENVIRONMENT=${{ env.ENVIRONMENT }} echo "ENVIRONMENT=${ENVIRONMENT}" >> $GITHUB_OUTPUT echo "STACK_NAME=${ENVIRONMENT//./-}" >> $GITHUB_OUTPUT - python3 -c 'import json; data = json.load(open("mrs.developer.json")); print("VOLTO_VERSION=" + data["core"]["tag"] or 'latest')' >> $GITHUB_OUTPUT + python3 -c 'import json; data = json.load(open("./mrs.developer.json")); print("VOLTO_VERSION=" + data["core"].get("tag") or "latest")' >> $GITHUB_OUTPUT - run: echo "${{ steps.vars.outputs.VOLTO_VERSION }}" @@ -72,7 +72,7 @@ jobs: with: platforms: linux/amd64 context: ./ - file: dockerfiles/Dockerfile + file: Dockerfile build-args: | ADDON_NAME=@kitconcept/volto-light-theme ADDON_PATH=volto-light-theme diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index 907c2fe9..fb72735e 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -2,7 +2,7 @@ name: i18n on: [push] env: - node-version: 20.x + NODE_VERSION: 20.x jobs: unit: @@ -12,18 +12,13 @@ jobs: - name: Main checkout uses: actions/checkout@v4 - - name: Use Node.js ${{ env.node-version }} + - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: ${{ env.node-version }} + node-version: ${{ env.NODE_VERSION }} - - uses: pnpm/action-setup@v3 - name: Install pnpm - with: - version: 8 - # We don't want to install until later, - # when the cache and Cypress are in place - run_install: false + - name: Enable corepack + run: corepack enable - name: Get pnpm store directory shell: bash diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 85cd5b54..e7737f01 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -2,7 +2,7 @@ name: Unit Tests on: [push] env: - node-version: 20.x + NODE_VERSION: 20.x jobs: unit: @@ -12,18 +12,13 @@ jobs: - name: Main checkout uses: actions/checkout@v4 - - name: Use Node.js ${{ env.node-version }} + - name: Use Node.js ${{ env.NODE_VERSION }} uses: actions/setup-node@v4 with: - node-version: ${{ env.node-version }} + node-version: ${{ env.NODE_VERSION }} - - uses: pnpm/action-setup@v3 - name: Install pnpm - with: - version: 8 - # We don't want to install until later, - # when the cache and Cypress are in place - run_install: false + - name: Enable corepack + run: corepack enable - name: Get pnpm store directory shell: bash diff --git a/.github/workflows/visual-acceptance.yaml b/.github/workflows/visual-acceptance.yaml index 6e39ed8a..0601b5d5 100644 --- a/.github/workflows/visual-acceptance.yaml +++ b/.github/workflows/visual-acceptance.yaml @@ -23,13 +23,8 @@ jobs: with: node-version: ${{ env.node-version }} - - uses: pnpm/action-setup@v3 - name: Install pnpm - with: - version: 8 - # We don't want to install until later, - # when the cache and Cypress are in place - run_install: false + - name: Enable corepack + run: corepack enable - name: Get pnpm store directory shell: bash @@ -91,7 +86,7 @@ jobs: # working-directory: backend - - run: make test-acceptance-visual-headless + - run: make ci-acceptance-test-visual # Upload Cypress screenshots - uses: actions/upload-artifact@v3 diff --git a/.gitignore b/.gitignore index 655ea2f9..01fd008a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ core .changelog.draft build .DS_Store +public diff --git a/BMv3-ready.svg b/BMv3-ready.svg new file mode 100644 index 00000000..e465f4df --- /dev/null +++ b/BMv3-ready.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index d723c7bb..b2f91255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,55 @@ +## 4.0.1 (2024-06-28) + +### Bugfix + +- Fix Invalid html structure in caption component @iRohitSingh [#398](https://github.com/kitconcept/volto-light-theme/pull/398) +- Fix install in Volto 17 @sneridagh [#400](https://github.com/kitconcept/volto-light-theme/pull/400) + +### Internal + +- Upgrade to Volto 18a37 @sneridagh [#403](https://github.com/kitconcept/volto-light-theme/pull/403) + +## 4.0.0 (2024-06-21) + +### Breaking + +- Fix tabbing order in the top header. It modifies the underlying HTML to move the top header to the bottom, and modifies CSS to adjust. @iRohitSingh @sneridagh [#374](https://github.com/kitconcept/volto-light-theme/pull/374) +- Updated the MobileNavigation component to be more easily customizable. + The component can now handle infinite navigation depth instead of only three levels, if configured to do so. + The Burger Menu can now be easily customized by overriding the new MobileNavigationToggler.jsx file. + @lenadax + + Breaking: + - The "hamburger" icon in the mobile navigation now has an additional wrapper that allows for better customization. + + If you have overriden the hamburger icon, you should make sure that your customizations still work and adjust otherwise. [#393](https://github.com/kitconcept/volto-light-theme/pull/393) + +### Bugfix + +- Fix Logo alt-Title @jonaspiterek [#337](https://github.com/kitconcept/volto-light-theme/pull/337) +- fix link in introduction block being smaller than normal text @jonaspiterek [#365](https://github.com/kitconcept/volto-light-theme/pull/365) +- Fix Description block width in Edit and Add mode. @danalvrz [#394](https://github.com/kitconcept/volto-light-theme/pull/394) + +### Internal + +- Update the setup. Use new images. @sneridagh [#390](https://github.com/kitconcept/volto-light-theme/pull/390) + +## 3.3.2 (2024-05-31) + +### Bugfix + +- Add top padding to login page @danalvrz [#387](https://github.com/kitconcept/volto-light-theme/pull/387) +- Add default width mixin to content creation Forms @danalvrz [#388](https://github.com/kitconcept/volto-light-theme/pull/388) + +## 3.3.1 (2024-05-30) + +### Bugfix + +- Fix image grid clossure issues in inlined JSX method when selecting an image after uploading another image in another grid element @sneridagh [#389](https://github.com/kitconcept/volto-light-theme/pull/389) + ## 3.3.0 (2024-04-26) ### Feature diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..66d269e3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# syntax=docker/dockerfile:1 +ARG VOLTO_VERSION +# TODO: Replace with +# FROM plone/frontend-builder:${VOLTO_VERSION} +# when the main image is ready +FROM ghcr.io/kitconcept/frontend-builder:${VOLTO_VERSION} as builder + +COPY --chown=node packages/volto-light-theme /app/packages/volto-light-theme +COPY --chown=node volto.config.js /app/ +COPY --chown=node package.json /app/package.json.temp + +RUN --mount=type=cache,id=pnpm,target=/app/.pnpm-store,uid=1000 < Start Docker-based Plone Backend$(RESET)" - docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE) + docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone $(DOCKER_IMAGE) + +## Storybook +.PHONY: storybook-start +storybook-start: ## Start Storybook server on port 6006 + @echo "$(GREEN)==> Start Storybook$(RESET)" + pnpm run storybook + +.PHONY: storybook-build +storybook-build: ## Build Storybook + @echo "$(GREEN)==> Build Storybook$(RESET)" + mkdir -p $(CURRENT_DIR)/.storybook-build + pnpm run build-storybook -o $(CURRENT_DIR)/.storybook-build ## Acceptance -.PHONY: start-test-acceptance-frontend-dev -start-test-acceptance-frontend-dev: ## Start acceptance frontend in dev mode +.PHONY: acceptance-frontend-dev-start +acceptance-frontend-dev-start: ## Start acceptance frontend in development mode RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm start -.PHONY: start-test-acceptance-frontend start-test-acceptance-frontend-visual -start-test-acceptance-frontend start-test-acceptance-frontend-visual: ## Start acceptance frontend in prod mode +.PHONY: acceptance-frontend-prod-start +acceptance-frontend-prod-start: ## Start acceptance frontend in production mode RAZZLE_API_PATH=http://127.0.0.1:55001/plone pnpm build && pnpm start:prod -.PHONY: start-test-acceptance-frontend-a11y -start-test-acceptance-frontend-a11y: ## Start a11y acceptance frontend in prod mode - pnpm build && pnpm start:prod - -.PHONY: start-test-acceptance-server start-test-acceptance-server-visual -start-test-acceptance-server start-test-acceptance-server-visual: ## Start acceptance server +.PHONY: acceptance-backend-start +acceptance-backend-start: ## Start backend acceptance server docker run -it --rm -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE) -.PHONY: start-test-acceptance-server-ci start-test-acceptance-server-visual-ci -start-test-acceptance-server-ci start-test-acceptance-server-visual-ci: ## Start acceptance server in CI mode (no terminal attached) +.PHONY: ci-acceptance-backend-start +ci-acceptance-backend-start: ## Start backend acceptance server in headless mode for CI docker run -i --rm -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE) -.PHONY: start-test-acceptance-server-a11y -start-test-acceptance-server-a11y: ## Start acceptance a11y server - docker run -it --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE) +.PHONY: acceptance-test +acceptance-test: ## Start Cypress in interactive mode + pnpm --filter @plone/volto exec cypress open --config-file $(CURRENT_DIR)/cypress.config.js --config specPattern=$(CURRENT_DIR)'/cypress/tests/main/**/*.{js,jsx,ts,tsx}' -.PHONY: start-test-acceptance-server-a11y-ci -start-test-acceptance-server-a11y-ci: ## Start acceptance a11y server in CI mode (no terminal attached) - docker run -i --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE) +.PHONY: ci-acceptance-test +ci-acceptance-test: ## Run cypress tests in headless mode for CI + pnpm --filter @plone/volto exec cypress run --config-file $(CURRENT_DIR)/cypress.config.js --config specPattern=$(CURRENT_DIR)'/cypress/tests/main/**/*.{js,jsx,ts,tsx}' -.PHONY: test-acceptance -test-acceptance: ## Start Cypress in interactive mode - pnpm exec cypress open --config specPattern=$(CURRENT_DIR)'/cypress/tests/main/**/*.{js,jsx,ts,tsx}' +# a11y tests +.PHONY: acceptance-a11y-frontend-prod-start +acceptance-a11y-frontend-prod-start: ## Start a11y acceptance frontend in prod mode + pnpm build && pnpm start:prod -.PHONY: test-acceptance-a11y -test-acceptance-a11y: ## Start a11y Cypress in interactive mode - CYPRESS_a11y=1 CYPRESS_API_PATH=http://localhost:8080/Plone pnpm exec cypress open specPattern=$(CURRENT_DIR)'/cypress/tests/a11y/**/*.{js,jsx,ts,tsx}' +.PHONY: ci-acceptance-a11y-backend-start +ci-acceptance-a11y-backend-start: ## Start acceptance a11y server in CI mode (no terminal attached) + docker run -i --rm --name=backend -p 8080:8080 -e SITE=Plone -e ADDONS='$(KGS)' $(DOCKER_IMAGE) -.PHONY: test-acceptance-headless -test-acceptance-headless: ## Run cypress tests in headless mode for CI - pnpm exec cypress run --config specPattern=$(CURRENT_DIR)'/cypress/tests/main/**/*.{js,jsx,ts,tsx}' +.PHONY: acceptance-a11y-test +acceptance-a11y-test: ## Start a11y Cypress in interactive mode + CYPRESS_a11y=1 CYPRESS_API_PATH=http://localhost:8080/Plone pnpm exec cypress open specPattern=$(CURRENT_DIR)'/cypress/tests/a11y/**/*.{js,jsx,ts,tsx}' -.PHONY: test-acceptance-headless-a11y -test-acceptance-headless-a11y: ## Run a11y cypress tests in headless mode for CI +.PHONY: ci-acceptance-a11y-test +ci-acceptance-a11y-test: ## Run a11y cypress tests in headless mode for CI CYPRESS_a11y=1 CYPRESS_API_PATH=http://localhost:8080/Plone pnpm exec cypress run --config specPattern=$(CURRENT_DIR)'/cypress/tests/a11y/**/*.{js,jsx,ts,tsx}' ### Visual acceptance tests ### @@ -129,41 +170,41 @@ test-acceptance-headless-a11y: ## Run a11y cypress tests in headless mode for CI # # -.PHONY: test-acceptance-visual -test-acceptance-visual: ## Start visual Cypress Acceptance Tests +.PHONY: acceptance-test-visual +acceptance-test-visual: ## Start visual Cypress Acceptance Tests NODE_ENV=production $(NODEBIN)/cypress open --config-file cypress/config/cypress.visual.config.js # Running the visual tests in headless, cumulative mode will SKIP tests that have run previously, and # only run the still failing tests HOWEVER the results are not collected in interactive mode due to # inherent limitations of Cypress. But if you run the tests in cumulative headless mode first, # then this command can be used to run ONLY the failing tests in interactive mode. -.PHONY: test-acceptance-visual-cumulative -test-acceptance-visual-cumulative: ## Start visual Cypress Acceptance Tests with cumulative filtering +.PHONY: acceptance-test-visual-cumulative +acceptance-test-visual-cumulative: ## Start visual Cypress Acceptance Tests with cumulative filtering @echo WARNING: cumulative results are NOT collected in interactive mode, use headless mode for collection NODE_ENV=production cypress_enableCumulative=true $(NODEBIN)/cypress open --config-file cypress/config/cypress.visual.config.js -.PHONY: test-acceptance-visual-headless -test-acceptance-visual-headless: ## Start visual Cypress Acceptance Tests in headless mode +.PHONY: ci-acceptance-test-visual +ci-acceptance-test-visual: ## Start visual Cypress Acceptance Tests in headless mode NODE_ENV=production $(NODEBIN)/cypress run --browser firefox --config-file cypress/config/cypress.visual.config.js # Running the visual tests in headless, cumulative mode will SKIP tests that have run previously, and # only run the still failing tests. -.PHONY: test-acceptance-visual-headless-cumulative -test-acceptance-visual-headless-cumulative: ## Start visual Cypress Acceptance Tests in headless mode +.PHONY: ci-acceptance-test-visual-cumulative +ci-acceptance-test-visual-cumulative: ## Start visual Cypress Acceptance Tests in headless mode NODE_ENV=production cypress_enableCumulative=true $(NODEBIN)/cypress run --browser firefox --config-file cypress/config/cypress.visual.config.js # Automatically update all changed visual snapshots -.PHONY: test-acceptance-visual-headless-update -test-acceptance-visual-headless-update: ## Start visual Cypress Acceptance Tests in headless mode, always pass and update images +.PHONY: ci-acceptance-test-visual-update +ci-acceptance-test-visual-update: ## Start visual Cypress Acceptance Tests in headless mode, always pass and update images NODE_ENV=production cypress_pluginVisualRegressionUpdateImages=true $(NODEBIN)/cypress run --browser firefox --config-file cypress/config/cypress.visual.config.js # Automatically update all changed visual snapshots, and # running the visual tests in headless, cumulative mode will SKIP tests that have run previously, and # only run the still failing tests -.PHONY: test-acceptance-visual-headless-update-cumulative -test-acceptance-visual-headless-update-cumulative: ## Start visual Cypress Acceptance Tests in headless mode, always pass and update images, with cumulative results +.PHONY: ci-acceptance-test-visual-update-cumulative +ci-acceptance-test-visual-update-cumulative: ## Start visual Cypress Acceptance Tests in headless mode, always pass and update images, with cumulative results NODE_ENV=production cypress_enableCumulative=true cypress_pluginVisualRegressionUpdateImages=true $(NODEBIN)/cypress run --browser firefox --config-file cypress/config/cypress.visual.config.js .PHONY: summarize-cumulative-state summarize-cumulative-state: ## Summarize cumulative state into ...-summary.report - yarn summarize-cumulative-state + pnpm summarize-cumulative-state diff --git a/README.md b/README.md index 4358494b..37091597 100644 --- a/README.md +++ b/README.md @@ -232,17 +232,24 @@ They will be noted properly in the changelog. See a detailed upgrade guide in: https://github.com/kitconcept/volto-light-theme/blob/main/UPGRADE-GUIDE.md -## Development +## Compatibility + +| VLT version | Volto version | +|-------------|---------------| +| 3.x.x | >= Volto 17.0.0-alpha.16 | +| 4.x.x | < Volto 17.18.0 | +| 5.x.x | >= Volto 17.18.0 or >=Volto 18.0.0-alpha.36 | -This theme works under Volto 17 alpha 16 onwards. -Compatibility with Volto 16 might be achieved, but it has to be at customization level in the -specific project add-on. +Compatibility with Volto 16 might be achieved, but it has to be at customization level in the specific project add-on. This is mainly due to the `RenderBlocks` customization that is based in the one in 17 because of the Grid block in core and the autogrouping feature. See more information about the other dependencies in `peerDependencies` in `package.json`. + +## Development + The development of this add-on is done in isolation using a new approach using pnpm workspaces and latest `mrs-developer` and other Volto core improvements. For this reason, it only works with pnpm and Volto 18 (currently in alpha) but it does not mean that the add-on will only work in 18. -### Requisites +### Development requisites - Volto 18 (2024-03-21: currently in alpha) - pnpm as package manager @@ -252,19 +259,32 @@ For this reason, it only works with pnpm and Volto 18 (currently in alpha) but i Run `make help` to list the available commands. ```text -help Show this help -install Installs the dev environment using mrs-developer -i18n Sync i18n -format Format codebase -lint Lint Codebase -test Run unit tests -test-ci Run unit tests in CI -start-backend-docker Starts a Docker-based backend for developing -start-test-acceptance-frontend-dev Start acceptance frontend in dev mode -start-test-acceptance-frontend Start acceptance frontend in prod mode -start-test-acceptance-server Start acceptance server -test-acceptance Start Cypress in interactive mode -test-acceptance-headless Run cypress tests in headless mode for CI +help Show this help +install Installs the add-on in a development environment +start Starts Volto, allowing reloading of the add-on during development +build Build a production bundle for distribution of the project with the add-on +build-deps Build dependencies +i18n Sync i18n +ci-i18n Check if i18n is not synced +format Format codebase +lint Lint, or catch and remove problems, in code base +release Release the add-on on npmjs.org +release-dry-run Dry-run the release of the add-on on npmjs.org +test Run unit tests +ci-test Run unit tests in CI +backend-docker-start Starts a Docker-based backend for development +storybook-start Start Storybook server on port 6006 +storybook-build Build Storybook +acceptance-frontend-dev-start Start acceptance frontend in development mode +acceptance-frontend-prod-start Start acceptance frontend in production mode +acceptance-backend-start Start backend acceptance server +ci-acceptance-backend-start Start backend acceptance server in headless mode for CI +acceptance-test Start Cypress in interactive mode +ci-acceptance-test Run cypress tests in headless mode for CI +acceptance-a11y-frontend-prod-start Start a11y acceptance frontend in prod mode +ci-acceptance-a11y-backend-start Start acceptance a11y server in CI mode (no terminal attached) +acceptance-a11y-test Start a11y Cypress in interactive mode +ci-acceptance-a11y-test Run a11y cypress tests in headless mode for CI ``` ### Development Environment Setup diff --git a/UPGRADE-GUIDE.md b/UPGRADE-GUIDE.md index 8f938601..88e83943 100644 --- a/UPGRADE-GUIDE.md +++ b/UPGRADE-GUIDE.md @@ -1,5 +1,24 @@ # Upgrade Guide +## volto-light-theme 5.0.0 + +The requirements for VLT have changed: + +| VLT version | Volto version | +|-------------|---------------| +| 3.x.x | >= Volto 17.0.0-alpha.16 | +| 4.x.x | < Volto 17.18.0 | +| 5.x.x | >= Volto 17.18.0 or >=Volto 18.0.0-alpha.36 | + +## volto-light-theme 4.0.0 + +The tabbing order in the top header was fixed for accessibility concerns. +It modifies the underlying HTML to move the top header to the bottom, and modifies CSS to adjust. + +The MobileNavigation component was updated to be more easily customizable. +The component can now handle infinite navigation depth instead of only three levels, if configured to do so. +The Burger Menu can now be easily customized by overriding the new MobileNavigationToggler.jsx file. + ## volto-light-theme 3.0.0 ### Blocks background colors go full width diff --git a/dockerfiles/Dockerfile b/dockerfiles/Dockerfile deleted file mode 100644 index 407c0005..00000000 --- a/dockerfiles/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -# syntax=docker/dockerfile:1 -ARG VOLTO_VERSION -FROM plone/frontend-builder:${VOLTO_VERSION} as builder - -ARG ADDON_NAME -ARG ADDON_PATH - -ENV THEME='@kitconcept/volto-light-theme' - -# Copy helper.py as /setupAddon -COPY dockerfiles/helper.py /setupAddon - -# Copy addon code -COPY --chown=node:node ./packages/${ADDON_PATH} /app/src/addons/${ADDON_PATH}/ - -# Install -RUN < dict: - """Add addons to the main `package.json`.""" - addons = config.get("addons", []) - workspaces = config.get("workspaces", []) - for pkg_name, pkg_path in packages.items(): - if not VOLTOCONFIGPATH.exists(): - addons.append(pkg_name) - workspace_path = pkg_path.replace("/src", "") - workspaces.append(f"src/{workspace_path}") - config["addons"] = addons - config["workspaces"] = workspaces - return config - - -def parse_jsonconfig(config: dict) -> dict: - """Parse existing `jsconfig.json`.""" - packages = {} - config_paths = config.get("compilerOptions", {}).get("paths", {}) - for pkg_name, pkg_path in config_paths.items(): - if isinstance(pkg_path, list): - packages[pkg_name] = pkg_path[0] - return packages - - -def parse_addon_name(addon_name: str) -> Tuple[str, str]: - """Parse the addon name and return also its probable path.""" - if addon_name.startswith("@"): - _, path = addon_name.split("/") - return addon_name, path - return addon_name, addon_name - - -def addon_to_package_json(config: dict, addon_name: str, addon_path: str) -> dict: - """Add a single addon to main `package.json`.""" - project_addons = config["addons"] - project_dependencies = config["dependencies"] - # Process package.json for the addon - workspace_path = f"src/addons/{addon_path}" - addon_path = (APP_FOLDER / workspace_path).resolve() - addon_config = json.load(open(addon_path / "package.json")) - # Process peerDependencies - peer_dependencies = addon_config.get("peerDependencies", {}) - for dependency_name, version in peer_dependencies.items(): - if dependency_name in project_dependencies: - continue - project_dependencies[dependency_name] = version - # Process peerAddons - peer_addons = addon_config.get("peerAddons", []) - for name in peer_addons: - if name in project_addons: - continue - project_addons.append(name) - # Add our addon to addons and to workspaces - project_addons.append(addon_name) - workspaces = config.get("workspaces", []) - workspaces.append(workspace_path) - - config["addons"] = project_addons - config["dependencies"] = project_dependencies - config["workspaces"] = workspaces - return config - - -def addon_to_jsconfig_json(config: dict, addon_name: str, addon_path: str) -> dict: - """Add a single addon to `jsconfig.json`.""" - if "compilerOptions" not in config: - config["compilerOptions"] = {} - if "paths" not in config: - config["compilerOptions"]["paths"] = {} - - config["compilerOptions"]["paths"][addon_name] = [f"addons/{addon_path}/src"] - return config - - -if ADDON_NAME: - logger.info("Processing the ADDON_NAME variable.") - SETTINGS = ( - (addon_to_package_json, PACKAGE_JSON_PATH), - (addon_to_jsconfig_json, JSCONFIG_PATH), - ) - addon_name, addon_path = parse_addon_name(ADDON_NAME) - for func, path in SETTINGS: - data = func(json.load(open(path)), addon_name=addon_name, addon_path=addon_path) - json.dump(data, open(path, "w"), indent=2) -else: - packages = {} - if JSCONFIG_PATH.exists(): - packages = parse_jsonconfig(json.load(open(JSCONFIG_PATH))) - - if not packages: - logger.warning("Existing jsconfig.json does not contain packages.") - else: - logger.info("Processing existing jsconfig.json.") - package_json = json.load(open(PACKAGE_JSON_PATH)) - # Process package_json - package_json = add_packages_to_package_json(package_json, packages) - json.dump(package_json, open(PACKAGE_JSON_PATH, "w"), indent=2) diff --git a/mrs.developer.json b/mrs.developer.json index 8c961a9d..a0322443 100644 --- a/mrs.developer.json +++ b/mrs.developer.json @@ -4,7 +4,7 @@ "package": "@plone/volto", "url": "git@github.com:plone/volto.git", "https": "https://github.com/plone/volto.git", - "tag": "18.0.0-alpha.25" + "tag": "18.0.0-alpha.39" }, "volto-button-block": { "develop": false, diff --git a/package.json b/package.json index a11380cd..28cdcb7e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "@kitconcept/volto-light-theme-dev", + "version": "4.0.1", "description": "Volto Light Theme by kitconcept", "main": "src/index.js", "types": "src/types/index.d.ts", @@ -26,14 +27,14 @@ }, "scripts": { "preinstall": "npx only-allow pnpm", - "start": "pnpm build:deps && VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto start", + "start": "VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto start", "start:prod": "pnpm --filter @plone/volto start:prod", - "build": "pnpm build:deps && VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto build", + "build": "VOLTOCONFIG=$(pwd)/volto.config.js pnpm --filter @plone/volto build", "build:deps": "pnpm --filter @plone/registry --filter @plone/components build", "i18n": "pnpm --filter addons i18n", "test": "RAZZLE_JEST_CONFIG=$(pwd)/jest-addon.config.js pnpm --filter @plone/volto test", - "lint": "eslint --max-warnings=0 'packages/**/src/**/*.{js,jsx,ts,tsx}'", - "lint:fix": "eslint --fix 'packages/**/src/**/*.{js,jsx,ts,tsx}'", + "lint": "VOLTOCONFIG=$(pwd)/volto.config.js eslint --max-warnings=0 'packages/**/src/**/*.{js,jsx,ts,tsx}'", + "lint:fix": "VOLTOCONFIG=$(pwd)/volto.config.js eslint --fix 'packages/**/src/**/*.{js,jsx,ts,tsx}'", "prettier": "prettier --check 'packages/**/src/**/*.{js,jsx,ts,tsx}'", "prettier:fix": "prettier --write 'packages/**/src/**/*.{js,jsx,ts,tsx}' ", "stylelint": "stylelint 'packages/**/src/**/*.{css,scss,less}' --allow-empty-input", @@ -49,9 +50,9 @@ "mrs-developer": "^2.2.0" }, "dependencies": { - "@plone/volto": "workspace:*", "@plone/registry": "workspace:*", + "@plone/volto": "workspace:*", "@kitconcept/volto-light-theme": "workspace:*" }, - "packageManager": "pnpm@8.15.4" -} + "packageManager": "pnpm@9.4.0" +} \ No newline at end of file diff --git a/packages/volto-light-theme/.release-it.json b/packages/volto-light-theme/.release-it.json index f49452fe..88edf2ae 100644 --- a/packages/volto-light-theme/.release-it.json +++ b/packages/volto-light-theme/.release-it.json @@ -1,13 +1,20 @@ { + "plugins": { + "../../core/packages/scripts/prepublish.js": {} + }, "hooks": { "after:bump": [ "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft", "pipx run towncrier build --yes --version ${version}", "cp ../../README.md ./ && cp CHANGELOG.md ../../CHANGELOG.md", - "git add ../../CHANGELOG.md" + "python3 -c 'import json; data = json.load(open(\"../../package.json\")); data[\"version\"] = \"${version}\"; json.dump(data, open(\"../../package.json\", \"w\"), indent=2)'", + "git add ../../CHANGELOG.md ../../package.json" ], "after:release": "rm .changelog.draft" }, + "npm": { + "publish": false + }, "git": { "changelog": "pipx run towncrier build --draft --yes --version 0.0.0", "requireUpstream": false, diff --git a/packages/volto-light-theme/CHANGELOG.md b/packages/volto-light-theme/CHANGELOG.md index d723c7bb..b2f91255 100644 --- a/packages/volto-light-theme/CHANGELOG.md +++ b/packages/volto-light-theme/CHANGELOG.md @@ -8,6 +8,55 @@ +## 4.0.1 (2024-06-28) + +### Bugfix + +- Fix Invalid html structure in caption component @iRohitSingh [#398](https://github.com/kitconcept/volto-light-theme/pull/398) +- Fix install in Volto 17 @sneridagh [#400](https://github.com/kitconcept/volto-light-theme/pull/400) + +### Internal + +- Upgrade to Volto 18a37 @sneridagh [#403](https://github.com/kitconcept/volto-light-theme/pull/403) + +## 4.0.0 (2024-06-21) + +### Breaking + +- Fix tabbing order in the top header. It modifies the underlying HTML to move the top header to the bottom, and modifies CSS to adjust. @iRohitSingh @sneridagh [#374](https://github.com/kitconcept/volto-light-theme/pull/374) +- Updated the MobileNavigation component to be more easily customizable. + The component can now handle infinite navigation depth instead of only three levels, if configured to do so. + The Burger Menu can now be easily customized by overriding the new MobileNavigationToggler.jsx file. + @lenadax + + Breaking: + - The "hamburger" icon in the mobile navigation now has an additional wrapper that allows for better customization. + + If you have overriden the hamburger icon, you should make sure that your customizations still work and adjust otherwise. [#393](https://github.com/kitconcept/volto-light-theme/pull/393) + +### Bugfix + +- Fix Logo alt-Title @jonaspiterek [#337](https://github.com/kitconcept/volto-light-theme/pull/337) +- fix link in introduction block being smaller than normal text @jonaspiterek [#365](https://github.com/kitconcept/volto-light-theme/pull/365) +- Fix Description block width in Edit and Add mode. @danalvrz [#394](https://github.com/kitconcept/volto-light-theme/pull/394) + +### Internal + +- Update the setup. Use new images. @sneridagh [#390](https://github.com/kitconcept/volto-light-theme/pull/390) + +## 3.3.2 (2024-05-31) + +### Bugfix + +- Add top padding to login page @danalvrz [#387](https://github.com/kitconcept/volto-light-theme/pull/387) +- Add default width mixin to content creation Forms @danalvrz [#388](https://github.com/kitconcept/volto-light-theme/pull/388) + +## 3.3.1 (2024-05-30) + +### Bugfix + +- Fix image grid clossure issues in inlined JSX method when selecting an image after uploading another image in another grid element @sneridagh [#389](https://github.com/kitconcept/volto-light-theme/pull/389) + ## 3.3.0 (2024-04-26) ### Feature diff --git a/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po b/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po index 10b2aae3..2766cccd 100644 --- a/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po +++ b/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po @@ -19,7 +19,12 @@ msgstr "" #. Default: "Back" #: components/MobileNavigation/MobileNavigation msgid "Back" -msgstr "" +msgstr "Zurück" + +#. Default: "Back to homepage" +#: components/Logo/Logo +msgid "Back to homepage" +msgstr "Zurück zur Startseite" #. Default: "Background color" #: components/Blocks/schema @@ -34,7 +39,7 @@ msgstr "Block Breite" #. Default: "Breadcrumbs" #: components/Breadcrumbs/Breadcrumbs msgid "Breadcrumbs" -msgstr "" +msgstr "Brotkrumen" #. Default: "Browse the site, drop an image, or type an URL" #: components/Blocks/Image/Edit @@ -44,7 +49,7 @@ msgstr "Bild auswählen, hochladen oder URL angeben" #. Default: "Button text" #: components/Blocks/Slider/schema msgid "Button text" -msgstr "" +msgstr "Button Text" #. Default: "Center" #: components/Widgets/AlignWidget @@ -65,7 +70,7 @@ msgstr "Kontakt" #. Default: "Continue reading" #: components/Blocks/Slider/DefaultBody msgid "Continue reading" -msgstr "" +msgstr "Weiterlesen" #. Default: "Copyright" #: components/Footer/Footer @@ -80,7 +85,7 @@ msgstr "Beschreibung" #. Default: "Distributed under the {license}." #: components/Footer/Footer msgid "Distributed under the {license}." -msgstr "" +msgstr "Vertrieben unter {license}" #. Default: "End" #: components/Blocks/EventMetadata/View @@ -100,18 +105,18 @@ msgstr "Volle Breite" #. Default: "GNU GPL license" #: components/Footer/Footer msgid "GNU GPL license" -msgstr "" +msgstr "GNU GPL Lizenz" #. Default: "Hide Button" #: components/Blocks/Slider/schema msgid "Hide Button" -msgstr "kein Button anzeigen" +msgstr "keinen Button anzeigen" #. Default: "Home" #: components/Breadcrumbs/Breadcrumbs #: components/MobileNavigation/MobileNavigation msgid "Home" -msgstr "" +msgstr "Startseite" #. Default: "ICS Download" #: components/Blocks/EventMetadata/View @@ -177,18 +182,13 @@ msgstr "Telefon" #. Default: "Please choose an existing content as source for this element" #: components/Blocks/Slider/DefaultBody msgid "Please choose an existing content as source for this element" -msgstr "" +msgstr "Bitte wählen sie einen bestehenden Inhalt als Quelle für dieses Element" #. Default: "Plone Foundation" #: components/Footer/Footer msgid "Plone Foundation" msgstr "" -#. Default: "Plone Site" -#: components/Logo/Logo -msgid "Plone Site" -msgstr "" - #. Default: "Plone{reg} Open Source CMS/WCM" #: components/Footer/Footer msgid "Plone{reg} Open Source CMS/WCM" @@ -272,7 +272,7 @@ msgstr "Sortierung" #. Default: "Source" #: components/Blocks/Slider/DefaultBody msgid "Source" -msgstr "" +msgstr "Quelle" #. Default: "Start" #: components/Blocks/EventMetadata/View @@ -317,7 +317,7 @@ msgstr "Bild löschen" #. Default: "Image preview" #: components/Blocks/Image/ImageSidebar msgid "image_block_preview" -msgstr "" +msgstr "Bild Vorschau" #. Default: "Loading" #: components/Blocks/Listing/ListingBody @@ -327,7 +327,7 @@ msgstr "laden" #. Default: "More info" #: components/Blocks/Slider/DefaultBody msgid "moreInfo" -msgstr "" +msgstr "Mehr Informationen" #. Default: "of" #: components/Blocks/Listing/ListingBody diff --git a/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po b/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po index 4e8cff53..8a4c9f5f 100644 --- a/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po +++ b/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po @@ -16,6 +16,11 @@ msgstr "" msgid "Back" msgstr "" +#. Default: "Back to homepage" +#: components/Logo/Logo +msgid "Back to homepage" +msgstr "" + #. Default: "Background color" #: components/Blocks/schema msgid "Background color" @@ -179,11 +184,6 @@ msgstr "" msgid "Plone Foundation" msgstr "" -#. Default: "Plone Site" -#: components/Logo/Logo -msgid "Plone Site" -msgstr "" - #. Default: "Plone{reg} Open Source CMS/WCM" #: components/Footer/Footer msgid "Plone{reg} Open Source CMS/WCM" diff --git a/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po b/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po index a4e675a7..2ed265ef 100644 --- a/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po +++ b/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po @@ -22,6 +22,11 @@ msgstr "" msgid "Back" msgstr "Atrás" +#. Default: "Back to homepage" +#: components/Logo/Logo +msgid "Back to homepage" +msgstr "" + #. Default: "Background color" #: components/Blocks/schema msgid "Background color" @@ -185,11 +190,6 @@ msgstr "Elija un contenido para utilizarlo como fuente de datos para este elemen msgid "Plone Foundation" msgstr "Fundación Plone" -#. Default: "Plone Site" -#: components/Logo/Logo -msgid "Plone Site" -msgstr "Sitio Plone" - #. Default: "Plone{reg} Open Source CMS/WCM" #: components/Footer/Footer msgid "Plone{reg} Open Source CMS/WCM" diff --git a/packages/volto-light-theme/locales/eu/LC_MESSAGES/volto.po b/packages/volto-light-theme/locales/eu/LC_MESSAGES/volto.po index 1bb6ce5d..dc9257e9 100644 --- a/packages/volto-light-theme/locales/eu/LC_MESSAGES/volto.po +++ b/packages/volto-light-theme/locales/eu/LC_MESSAGES/volto.po @@ -22,6 +22,11 @@ msgstr "" msgid "Back" msgstr "Atzera" +#. Default: "Back to homepage" +#: components/Logo/Logo +msgid "Back to homepage" +msgstr "" + #. Default: "Background color" #: components/Blocks/schema msgid "Background color" @@ -185,11 +190,6 @@ msgstr "Aukeratu eduki bat elementu honen iturburu gisa" msgid "Plone Foundation" msgstr "Plone Fundazioa" -#. Default: "Plone Site" -#: components/Logo/Logo -msgid "Plone Site" -msgstr "Plone Ataria" - #. Default: "Plone{reg} Open Source CMS/WCM" #: components/Footer/Footer msgid "Plone{reg} Open Source CMS/WCM" diff --git a/packages/volto-light-theme/locales/pt_BR/volto.po b/packages/volto-light-theme/locales/pt_BR/volto.po index 38e50fdb..5101ed2b 100644 --- a/packages/volto-light-theme/locales/pt_BR/volto.po +++ b/packages/volto-light-theme/locales/pt_BR/volto.po @@ -16,6 +16,11 @@ msgstr "" msgid "Back" msgstr "Voltar" +#. Default: "Back to homepage" +#: components/Logo/Logo +msgid "Back to homepage" +msgstr "Voltar à página inicial" + #. Default: "Background color" #: components/Blocks/schema msgid "Background color" @@ -179,11 +184,6 @@ msgstr "Por favor, escolha um conteúdo existente como fonte para esse elemento" msgid "Plone Foundation" msgstr "Plone Foundation" -#. Default: "Plone Site" -#: components/Logo/Logo -msgid "Plone Site" -msgstr "Site Plone" - #. Default: "Plone{reg} Open Source CMS/WCM" #: components/Footer/Footer msgid "Plone{reg} Open Source CMS/WCM" diff --git a/packages/volto-light-theme/locales/volto.pot b/packages/volto-light-theme/locales/volto.pot index 171d0521..d889c448 100644 --- a/packages/volto-light-theme/locales/volto.pot +++ b/packages/volto-light-theme/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2024-03-22T15:33:53.504Z\n" +"POT-Creation-Date: 2024-05-31T09:46:26.312Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "Content-Type: text/plain; charset=utf-8\n" @@ -18,6 +18,11 @@ msgstr "" msgid "Back" msgstr "" +#. Default: "Back to homepage" +#: components/Logo/Logo +msgid "Back to homepage" +msgstr "" + #. Default: "Background color" #: components/Blocks/schema msgid "Background color" @@ -181,11 +186,6 @@ msgstr "" msgid "Plone Foundation" msgstr "" -#. Default: "Plone Site" -#: components/Logo/Logo -msgid "Plone Site" -msgstr "" - #. Default: "Plone{reg} Open Source CMS/WCM" #: components/Footer/Footer msgid "Plone{reg} Open Source CMS/WCM" diff --git a/packages/volto-light-theme/news/405.breaking b/packages/volto-light-theme/news/405.breaking new file mode 100644 index 00000000..6bd52a28 --- /dev/null +++ b/packages/volto-light-theme/news/405.breaking @@ -0,0 +1 @@ +Upgrade to a39, enable new image widget @sneridagh diff --git a/packages/volto-light-theme/package.json b/packages/volto-light-theme/package.json index d17a63bc..d8458d17 100644 --- a/packages/volto-light-theme/package.json +++ b/packages/volto-light-theme/package.json @@ -1,6 +1,6 @@ { "name": "@kitconcept/volto-light-theme", - "version": "3.3.0", + "version": "4.0.1", "description": "Volto Light Theme by kitconcept", "main": "src/index.js", "types": "src/types/index.d.ts", @@ -37,17 +37,8 @@ "@plone/scripts": "^3.6.1", "release-it": "^17.1.1" }, - "peerAddons": [ - "@eeacms/volto-accordion-block", - "@kitconcept/volto-button-block", - "@kitconcept/volto-heading-block", - "@kitconcept/volto-highlight-block", - "@kitconcept/volto-introduction-block", - "@kitconcept/volto-separator-block", - "@kitconcept/volto-slider-block" - ], "dependencies": { - "@plone/components": "2.0.0-alpha.6" + "@plone/components": "workspace:*" }, "peerDependencies": { "@eeacms/volto-accordion-block": "^10.4.6", diff --git a/packages/volto-light-theme/src/components/Blocks/Image/Edit.jsx b/packages/volto-light-theme/src/components/Blocks/Image/Edit.jsx index 2d860906..ea3d75dc 100644 --- a/packages/volto-light-theme/src/components/Blocks/Image/Edit.jsx +++ b/packages/volto-light-theme/src/components/Blocks/Image/Edit.jsx @@ -1,259 +1,52 @@ -/** - * Edit image block. - * @module components/manage/Blocks/Image/Edit - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { compose } from 'redux'; -import { readAsDataURL } from 'promise-file-reader'; -import { Button, Dimmer, Input, Loader, Message } from 'semantic-ui-react'; -import { defineMessages, injectIntl } from 'react-intl'; -import loadable from '@loadable/component'; +import React from 'react'; import cx from 'classnames'; -import { isEqual } from 'lodash'; - -import Caption from '../../Caption/Caption'; +import { ImageSidebar, SidebarPortal } from '@plone/volto/components'; -import { Icon, ImageSidebar, SidebarPortal } from '@plone/volto/components'; -import { createContent } from '@plone/volto/actions'; import { flattenToAppURL, - getBaseUrl, isInternalURL, withBlockExtensions, - validateFileUploadSize, } from '@plone/volto/helpers'; import config from '@plone/volto/registry'; -import imageBlockSVG from '@plone/volto/components/manage/Blocks/Image/block-image.svg'; -import clearSVG from '@plone/volto/icons/clear.svg'; -import navTreeSVG from '@plone/volto/icons/nav.svg'; -import aheadSVG from '@plone/volto/icons/ahead.svg'; -import uploadSVG from '@plone/volto/icons/upload.svg'; - -const Dropzone = loadable(() => import('react-dropzone')); - -const messages = defineMessages({ - ImageBlockInputPlaceholder: { - id: 'Browse the site, drop an image, or type an URL', - defaultMessage: 'Browse the site, drop an image, or type an URL', - }, - uploadingImage: { - id: 'Uploading image', - defaultMessage: 'Uploading image', - }, -}); - -/** - * Edit image block class. - * @class Edit - * @extends Component - */ -class Edit extends Component { - /** - * Property types. - * @property {Object} propTypes Property types. - * @static - */ - static propTypes = { - selected: PropTypes.bool.isRequired, - block: PropTypes.string.isRequired, - index: PropTypes.number.isRequired, - data: PropTypes.objectOf(PropTypes.any).isRequired, - content: PropTypes.objectOf(PropTypes.any), - request: PropTypes.shape({ - loading: PropTypes.bool, - loaded: PropTypes.bool, - }).isRequired, - pathname: PropTypes.string.isRequired, - onChangeBlock: PropTypes.func.isRequired, - onSelectBlock: PropTypes.func.isRequired, - onDeleteBlock: PropTypes.func.isRequired, - onFocusPreviousBlock: PropTypes.func.isRequired, - onFocusNextBlock: PropTypes.func.isRequired, - handleKeyDown: PropTypes.func.isRequired, - createContent: PropTypes.func.isRequired, - openObjectBrowser: PropTypes.func.isRequired, - }; - - state = { - uploading: false, - url: '', - dragging: false, - }; +import { ImageInput } from '@plone/volto/components/manage/Widgets/ImageWidget'; +import Caption from '../../Caption/Caption'; - /** - * Component will receive props - * @method componentWillReceiveProps - * @param {Object} nextProps Next properties - * @returns {undefined} - */ - UNSAFE_componentWillReceiveProps(nextProps) { - // Update block data after upload finished - if ( - this.props.request.loading && - nextProps.request.loaded && - this.state.uploading - ) { - this.setState({ - uploading: false, +function Edit(props) { + const { data } = props; + const Image = config.getComponent({ name: 'Image' }).component; + const onSelectItem = React.useCallback( + (url, item) => { + const dataAdapter = props.blocksConfig[props.data['@type']].dataAdapter; + dataAdapter({ + block: props.block, + data: props.data, + onChangeBlock: props.onChangeBlock, + id: 'url', + value: url, + item, }); - this.props.onChangeBlock(this.props.block, { - ...this.props.data, - url: nextProps.content['@id'], - image_field: 'image', - image_scales: { image: [nextProps.content.image] }, - alt: '', + }, + [props], + ); + + const handleChange = React.useCallback( + async (id, image, { title, image_field, image_scales } = {}) => { + const url = image ? image['@id'] || image : ''; + + props.onChangeBlock(props.block, { + ...props.data, + url: flattenToAppURL(url), + image_field, + image_scales, + alt: props.data.alt || title || '', }); - } - } - - /** - * @param {*} nextProps - * @returns {boolean} - * @memberof Edit - */ - shouldComponentUpdate(nextProps) { - return ( - this.props.selected || - nextProps.selected || - !isEqual(this.props.data, nextProps.data) - ); - } - - /** - * Upload image handler (not used), but useful in case that we want a button - * not powered by react-dropzone - * @method onUploadImage - * @returns {undefined} - */ - onUploadImage = (e) => { - e.stopPropagation(); - const file = e.target.files[0]; - if (!validateFileUploadSize(file, this.props.intl.formatMessage)) return; - this.setState({ - uploading: true, - }); - readAsDataURL(file).then((data) => { - const fields = data.match(/^data:(.*);(.*),(.*)$/); - this.props.createContent( - getBaseUrl(this.props.pathname), - { - '@type': 'Image', - title: file.name, - image: { - data: fields[3], - encoding: fields[2], - 'content-type': fields[1], - filename: file.name, - }, - }, - this.props.block, - ); - }); - }; - - /** - * Change url handler - * @method onChangeUrl - * @param {Object} target Target object - * @returns {undefined} - */ - onChangeUrl = ({ target }) => { - this.setState({ - url: target.value, - }); - }; + }, + [props], + ); - /** - * Submit url handler - * @method onSubmitUrl - * @param {object} e Event - * @returns {undefined} - */ - onSubmitUrl = () => { - this.props.onChangeBlock(this.props.block, { - ...this.props.data, - url: flattenToAppURL(this.state.url), - }); - }; - - /** - * Drop handler - * @method onDrop - * @param {array} files File objects - * @returns {undefined} - */ - onDrop = (files) => { - if (!validateFileUploadSize(files[0], this.props.intl.formatMessage)) { - this.setState({ dragging: false }); - return; - } - this.setState({ uploading: true }); - - readAsDataURL(files[0]).then((data) => { - const fields = data.match(/^data:(.*);(.*),(.*)$/); - this.props.createContent( - getBaseUrl(this.props.pathname), - { - '@type': 'Image', - title: files[0].name, - image: { - data: fields[3], - encoding: fields[2], - 'content-type': fields[1], - filename: files[0].name, - }, - }, - this.props.block, - ); - }); - }; - - /** - * Keydown handler on Variant Menu Form - * This is required since the ENTER key is already mapped to a onKeyDown - * event and needs to be overriden with a child onKeyDown. - * @method onKeyDownVariantMenuForm - * @param {Object} e Event object - * @returns {undefined} - */ - onKeyDownVariantMenuForm = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); - e.stopPropagation(); - this.onSubmitUrl(); - } else if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - // TODO: Do something on ESC key - } - }; - onDragEnter = () => { - this.setState({ dragging: true }); - }; - onDragLeave = () => { - this.setState({ dragging: false }); - }; - - node = React.createRef(); - - /** - * Render method. - * @method render - * @returns {string} Markup for the component. - */ - render() { - const Image = config.getComponent({ name: 'Image' }).component; - const { block, data, onChangeBlock } = this.props; - const placeholder = - this.props.data.placeholder || - this.props.intl.formatMessage(messages.ImageBlockInputPlaceholder); - const dataAdapter = this.props.blocksConfig[data['@type']].dataAdapter; - - return ( + return ( + <>
{data.url ? ( - // START CUSTOMIZATION - Added `figure` tag
- {/* // END CUSTOMIZATION - Added `figure` tag */}
) : ( -
- {this.props.editable && ( - - {({ getRootProps, getInputProps }) => ( -
- - {this.state.dragging && } - {this.state.uploading && ( - - - {this.props.intl.formatMessage( - messages.uploadingImage, - )} - - - )} -
- -
- - - - - - - { - e.target.focus(); - }} - onFocus={(e) => { - this.props.onSelectBlock(this.props.id); - }} - /> - {this.state.url && ( - - - - )} - - - -
-
-
-
- )} -
- )} -
+ )} - - + +
- ); - } + + ); } -export default compose( - injectIntl, - withBlockExtensions, - connect( - (state, ownProps) => ({ - request: state.content.subrequests[ownProps.block] || {}, - content: state.content.subrequests[ownProps.block]?.data, - }), - { createContent }, - ), -)(Edit); +export default withBlockExtensions(Edit); diff --git a/packages/volto-light-theme/src/components/Caption/Caption.jsx b/packages/volto-light-theme/src/components/Caption/Caption.jsx index 88f84cd3..3ad3aadb 100644 --- a/packages/volto-light-theme/src/components/Caption/Caption.jsx +++ b/packages/volto-light-theme/src/components/Caption/Caption.jsx @@ -16,16 +16,15 @@ import PropTypes from 'prop-types'; */ const Caption = ({ as = 'figcaption', title, description, credit }) => { const As = as; - return ( {title &&

{title}

} {description && ( -

+

{description.split('\n').map((line, index) => (

{line || '\u00A0'}

))} -

+
)} {credit &&

{credit}

}
diff --git a/packages/volto-light-theme/src/components/Header/Header.jsx b/packages/volto-light-theme/src/components/Header/Header.jsx index 7b66241b..83b96e0e 100644 --- a/packages/volto-light-theme/src/components/Header/Header.jsx +++ b/packages/volto-light-theme/src/components/Header/Header.jsx @@ -28,18 +28,6 @@ const InternetHeader = ({ pathname, siteLabel, token, siteAction }) => { return ( <>
-
-
- -
- - -
-
- -
-
-
@@ -58,6 +46,18 @@ const InternetHeader = ({ pathname, siteLabel, token, siteAction }) => {
)}
+
+
+ +
+ + +
+
+ +
+
+
); diff --git a/packages/volto-light-theme/src/components/Logo/Logo.jsx b/packages/volto-light-theme/src/components/Logo/Logo.jsx index 8718c09d..eb21078a 100644 --- a/packages/volto-light-theme/src/components/Logo/Logo.jsx +++ b/packages/volto-light-theme/src/components/Logo/Logo.jsx @@ -12,9 +12,9 @@ const messages = defineMessages({ id: 'Site', defaultMessage: 'Site', }, - plonesite: { - id: 'Plone Site', - defaultMessage: 'Plone Site', + homepage: { + id: 'Back to homepage', + defaultMessage: 'Back to homepage', }, }); @@ -35,8 +35,8 @@ const Logo = () => { ? flattenToAppURL(site['plone.site_logo']) : LogoImage } - alt={intl.formatMessage(messages.plonesite)} - title={intl.formatMessage(messages.plonesite)} + alt={intl.formatMessage(messages.homepage)} + title={intl.formatMessage(messages.homepage)} /> ); diff --git a/packages/volto-light-theme/src/components/MobileNavigation/MobileNavigation.jsx b/packages/volto-light-theme/src/components/MobileNavigation/MobileNavigation.jsx index d46185e8..ea32829f 100644 --- a/packages/volto-light-theme/src/components/MobileNavigation/MobileNavigation.jsx +++ b/packages/volto-light-theme/src/components/MobileNavigation/MobileNavigation.jsx @@ -1,15 +1,17 @@ -import React, { useCallback } from 'react'; +import { useCallback, useState, useEffect, useRef } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useSelector } from 'react-redux'; import { Link, useHistory } from 'react-router-dom'; import cx from 'classnames'; import { CSSTransition } from 'react-transition-group'; +import { doesNodeContainClick } from 'semantic-ui-react/dist/commonjs/lib'; import config from '@plone/volto/registry'; import { Icon, SearchWidget } from '@plone/volto/components'; import { toBackendLang } from '@plone/volto/helpers'; import arrowRightSVG from '@plone/volto/icons/right-key.svg'; import arrowLeftSVG from '@plone/volto/icons/left-key.svg'; +import { MobileNavigationToggler } from './MobileNavigationToggler'; import { MobileToolsFooter } from './MobileToolsFooter'; const messages = defineMessages({ @@ -25,27 +27,106 @@ const messages = defineMessages({ id: 'Search', defaultMessage: 'Search', }, + back: { + id: 'Back', + defaultMessage: 'Back', + }, }); -const MobileNavigation = (props) => { - const [menuState, setMenuState] = React.useState({ - isMobileMenuOpen: false, - secondaryMenuOpened: null, - isSecondaryMobileMenuOpen: false, - tertiaryMenuOpened: null, - isTertiaryMobileMenuOpen: false, - }); +const MenuItem = ({ + section, + level, + closeMenus, + handleLinkClicked, + resetToRoot, + pathname, +}) => { + const [isSubMenuOpen, setSubMenuOpen] = useState(false); + + const openSubMenu = useCallback((e) => { + e.stopPropagation(); + setSubMenuOpen(true); + }, []); + + const closeSubMenu = useCallback((e) => { + e.stopPropagation(); + setSubMenuOpen(false); + }, []); + + // Reset submenus when history changes + useEffect(() => { + if (resetToRoot) { + setSubMenuOpen(false); + } + }, [resetToRoot]); + + return ( +
  • + + handleLinkClicked(e, section, openSubMenu, closeSubMenu, level) + } + > + {section.nav_title || section.title} + {section.items.length > 0 && } + + +
    +
    +
    +
    + +
    +
    + +
    +
      +
    • {section.nav_title || section.title}
    • +
    • + + + +
    • + {section.items && + section.items.map((subsection, index) => ( + + ))} +
    + +
    +
    +
  • + ); +}; - const { - isMobileMenuOpen, - secondaryMenuOpened, - isSecondaryMobileMenuOpen, - tertiaryMenuOpened, - isTertiaryMobileMenuOpen, - } = menuState; +const MobileNavigation = (props) => { + const [isMobileMenuOpen, setMobileMenuOpen] = useState(false); + const [resetToRoot, setResetToRoot] = useState(false); const { settings } = config; const intl = useIntl(); - const menus = React.useRef(null); + const menus = useRef(null); const currentLang = useSelector((state) => state.intl.locale); const items = useSelector((state) => state.navigation.items || []); const history = useHistory(); @@ -55,94 +136,64 @@ const MobileNavigation = (props) => { const toggleMobileMenu = useCallback(() => { const body = document.getElementsByTagName('body')[0]; body.classList.toggle('has-menu-open'); - - setMenuState((prevState) => ({ - ...prevState, - isMobileMenuOpen: !prevState.isMobileMenuOpen, - })); - }, []); - - const openSecondaryMenu = useCallback((e, index) => { - e.stopPropagation(); - - setMenuState((prevState) => ({ - ...prevState, - secondaryMenuOpened: index, - isSecondaryMobileMenuOpen: true, - })); - }, []); - - const closeSecondaryMenu = useCallback((e) => { - e.stopPropagation(); - setMenuState((prevState) => ({ - ...prevState, - isSecondaryMobileMenuOpen: false, - secondaryMenuOpened: null, - })); - }, []); - - const openTertiaryMenu = useCallback((e, index) => { - e.stopPropagation(); - - setMenuState((prevState) => ({ - ...prevState, - tertiaryMenuOpened: index, - isTertiaryMobileMenuOpen: true, - })); - }, []); - - const closeTertiaryMenu = useCallback((e) => { - e.stopPropagation(); - - setMenuState((prevState) => ({ - ...prevState, - isTertiaryMobileMenuOpen: false, - tertiaryMenuOpened: null, - })); - }, []); + setMobileMenuOpen((prev) => !prev); + if (!isMobileMenuOpen) { + setResetToRoot(false); // Disable reset to root when opening menu + } + }, [isMobileMenuOpen]); const closeMenus = useCallback((e) => { if (e && e.stopPropagation) { e.stopPropagation(); } - - setMenuState(() => ({ - isSecondaryMobileMenuOpen: false, - isTertiaryMobileMenuOpen: false, - secondaryMenuOpened: null, - tertiaryMenuOpened: null, - isMobileMenuOpen: false, - })); + setMobileMenuOpen(false); }, []); const handleLinkClicked = useCallback( - (e, section, callback, index) => { + (e, section, openSubMenu, closeSubMenu, level) => { e.preventDefault(); if (section.items.length > 0) { - callback(e, index); + openSubMenu(e); } else { history.push(section.url); - return closeMenus(e); + closeMenus(e); + setResetToRoot(true); } }, [history, closeMenus], ); - React.useEffect(() => { - const closeMenuOnHistoryChange = history.listen(() => closeMenus({})); + useEffect(() => { + const closeMenuOnHistoryChange = history.listen(() => { + closeMenus({}); + setResetToRoot(true); + }); return () => { closeMenuOnHistoryChange(); }; }, [history, closeMenus]); + useEffect(() => { + const handleClickOutside = (e) => { + if (menus.current && !doesNodeContainClick(menus.current, e)) { + closeMenus(e); + } + }; + document.addEventListener('mousedown', handleClickOutside, false); + + return () => { + document.removeEventListener('mousedown', handleClickOutside, false); + }; + }, [closeMenus]); + return (
    @@ -169,7 +218,7 @@ const MobileNavigation = (props) => { >
    -
    +
    @@ -190,169 +239,15 @@ const MobileNavigation = (props) => { {items && items.map((section, index) => ( -
  • - - handleLinkClicked(e, section, openSecondaryMenu, index) - } - > - {section.nav_title || section.title} - {section.items.length > 0 && } - - -
    -
    -
    -
    - -
    -
    - - -
    -
      -
    • - {section.nav_title || section.title} -
    • -
    • - - - -
    • - - {section.items && - section.items.map((subsection, index) => ( -
    • - - handleLinkClicked( - e, - subsection, - openTertiaryMenu, - index, - ) - } - > - {subsection.nav_title || subsection.title} - {subsection.items.length > 0 && ( - - )} - - -
      -
      -
      -
      - -
      -
      - - -
      -
        -
      • - {subsection.nav_title || subsection.title} -
      • -
      • - - - -
      • - {subsection.items && - subsection.items.map( - (subsubsection, subindex) => ( -
      • - - {subsubsection.nav_title || - subsubsection.title} - -
      • - ), - )} -
      -
      -
      -
      -
    • - ))} -
    -
    -
    -
    -
  • + section={section} + level={0} + closeMenus={closeMenus} + handleLinkClicked={handleLinkClicked} + resetToRoot={resetToRoot} + pathname={props.pathname} + /> ))}