diff --git a/.eslintignore b/.eslintignore index 7df5c928c1..77fb2df91b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,3 +4,5 @@ packages/volto packages/volto-guillotina !.* dist +packages/registry/lib +packages/registry/docs diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..24ab7677d5 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +- [ ] I signed and returned the [Plone Contributor Agreement](https://plone.org/foundation/contributors-agreement), and received and accepted an invitation to join a team in the Plone GitHub organization. +- [ ] I verified there aren't other open [pull requests](https://github.com/plone/volto/pulls) for the same change. +- [ ] I followed the guidelines in [Contributing to Volto](https://6.docs.plone.org/volto/contributing/index.html). +- [ ] I succesfully ran [code linting checks](https://6.docs.plone.org/volto/contributing/linting.html) on my changes locally. +- [ ] I succesfully ran [unit tests](https://6.docs.plone.org/volto/contributing/testing.html) on my changes locally. +- [ ] I succesfully ran [acceptance tests](https://6.docs.plone.org/volto/contributing/acceptance-tests.html) on my changes locally. +- [ ] If needed, I added new tests for my changes. +- [ ] If needed, I added [documentation](https://6.docs.plone.org/volto/contributing/documentation.html#narrative-documentation) for my changes, either in the Storybook or narrative documentation. +- [ ] I included a [change log entry](https://6.docs.plone.org/contributing/index.html#contributing-change-log-label) in my commits. + +----- + +If your pull request closes an open issue, include the exact text below, immediately followed by the issue number. When your pull request gets merged, then that issue will close automatically. + +Closes # diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 6d93708898..82480849c7 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -57,7 +57,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -105,7 +105,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -153,7 +153,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -201,7 +201,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -249,7 +249,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -296,7 +296,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [22.x] steps: - uses: actions/checkout@v4 @@ -344,7 +344,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [22.x] steps: - uses: actions/checkout@v4 @@ -392,7 +392,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [22.x] steps: - uses: actions/checkout@v4 @@ -440,7 +440,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [22.x] # python-version: [3.7] steps: - uses: actions/checkout@v4 @@ -489,7 +489,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] env: generator-directory: ./packages/generator-volto project-directory: ./my-volto-app @@ -521,6 +521,9 @@ jobs: - name: Install yalc run: npm -g install yalc + - name: Build dependencies + run: make build-deps + - name: Install a yalc'ed version of the current Volto in the project - publish run: | yalc publish packages/types @@ -597,7 +600,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [22.x] steps: - uses: actions/checkout@v4 @@ -647,7 +650,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x] + node-version: [22.x] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 47fa637159..418c2c4f18 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -6,7 +6,7 @@ on: - main env: - node-version: 20.x + node-version: 22.x jobs: towncrier: diff --git a/.github/workflows/code-analysis.yml b/.github/workflows/code-analysis.yml index b0519c45da..3878529afb 100644 --- a/.github/workflows/code-analysis.yml +++ b/.github/workflows/code-analysis.yml @@ -2,7 +2,7 @@ name: Code Analysis Check on: [push, pull_request] env: - node-version: 20.x + node-version: 22.x jobs: prettier: diff --git a/.github/workflows/deployment_tests.yml b/.github/workflows/deployment_tests.yml index 5f22b40b67..9f6edc6e22 100644 --- a/.github/workflows/deployment_tests.yml +++ b/.github/workflows/deployment_tests.yml @@ -2,7 +2,7 @@ name: Deployment Tests on: [push, pull_request] env: - node-version: 20.x + node-version: 22.x jobs: vitessr: diff --git a/.github/workflows/docs-rtd-pr-preview.yml b/.github/workflows/docs-rtd-pr-preview.yml index 83d7e72494..a123d8ef75 100644 --- a/.github/workflows/docs-rtd-pr-preview.yml +++ b/.github/workflows/docs-rtd-pr-preview.yml @@ -10,6 +10,7 @@ on: - "docs/source/**" - .readthedocs.yaml - requirements-docs.txt + - "packages/registry/docs/**" permissions: pull-requests: write diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 75da9c3140..0c868b5779 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -10,6 +10,9 @@ on: paths: - 'docs/**' - 'styles/**' + - '.github/workflows/docs.yml' + - 'requirements-docs.txt' + - '.vale.ini' jobs: docs: @@ -32,9 +35,7 @@ jobs: run: pip install virtualenv - name: pip install requirements - run: | - pip install -r requirements-docs.txt - sudo snap install --edge vale + run: pip install -r requirements-docs.txt - name: Check for broken links run: make docs-linkcheckbroken @@ -43,7 +44,4 @@ jobs: run: make docs-html - name: Run vale - run: | - git clone https://github.com/errata-ai/Microsoft.git - cp -r ./Microsoft/Microsoft ./styles - vale --no-exit ./docs + run: make docs-vale VALEOPTS=--no-exit diff --git a/.github/workflows/readme-link-check.yml b/.github/workflows/readme-link-check.yml index 7f53b65824..42c96e1455 100644 --- a/.github/workflows/readme-link-check.yml +++ b/.github/workflows/readme-link-check.yml @@ -20,4 +20,4 @@ jobs: - name: Check links in README.md with awesome_bot run: | gem install awesome_bot - awesome_bot --request-delay 1 --allow-dupe --white-list http://localhost:8080/Plone,http://localhost:3000,https://github.com/kitconcept/volto-blocks-grid.git,https://my-server-DNS-name.tld/api --files PACKAGES.md,README.md,packages/blocks/README.md,packages/client/README.md,packages/components/README.md,packages/generator-volto/README.md,packages/registry/README.md,packages/scripts/README.md,packages/tsconfig/README.md,packages/types/README.md,packages/volto-slate/README.md,apps/nextjs/README.md,apps/remix/README.md,apps/vite-ssr/README.md + awesome_bot --request-delay 1 --allow-dupe --white-list http://localhost:8080/Plone,http://localhost:8080,http://localhost:3000,https://github.com/kitconcept/volto-blocks-grid.git,https://my-server-DNS-name.tld/api --files PACKAGES.md,README.md,packages/blocks/README.md,packages/client/README.md,packages/components/README.md,packages/generator-volto/README.md,packages/registry/README.md,packages/scripts/README.md,packages/tsconfig/README.md,packages/types/README.md,packages/volto-slate/README.md,apps/nextjs/README.md,apps/remix/README.md,apps/vite-ssr/README.md diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index b479cc7fb1..138349a40b 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -2,7 +2,7 @@ name: Unit Tests on: [push, pull_request] env: - node-version: 20.x + node-version: 22.x jobs: volto: @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -40,7 +40,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [18.x, 20.x] + node-version: [20.x, 22.x] steps: - uses: actions/checkout@v4 @@ -77,16 +77,14 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - node-version: [18.x, 20.x] steps: - uses: actions/checkout@v4 # node setup - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js ${{ env.node-version }} uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: ${{ env.node-version }} - name: Enable corepack run: corepack enable @@ -152,16 +150,14 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - node-version: [18.x, 20.x] steps: - uses: actions/checkout@v4 # node setup - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js ${{ env.node-version }} uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: ${{ env.node-version }} - name: Enable corepack run: corepack enable diff --git a/.prettierignore b/.prettierignore index cf39cfcd94..1749ca9b36 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,6 +5,7 @@ styles/rules/* node_modules packages/volto/types/* +packages/registry/docs/* storybook-static apps/vite-ssr/src/routeTree.gen.ts apps/vite/src/routeTree.gen.ts diff --git a/Makefile b/Makefile index 4ff9cb9510..72656906b6 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,7 @@ include variables.mk # Sphinx variables # You can set these variables from the command line. SPHINXOPTS ?= +VALEOPTS ?= # Internal variables. SPHINXBUILD = "$(realpath bin/sphinx-build)" SPHINXAUTOBUILD = "$(realpath bin/sphinx-autobuild)" @@ -66,7 +67,7 @@ test: ## Run unit tests .PHONY: clean clean: ## Clean development environment rm -rf node_modules - find ./packages -name node_modules -exec rm -rf {} \; + find ./packages -name node_modules -not -path "./packages/volto/__tests__/*" -exec rm -rf {} \; .PHONY: install install: ## Set up development environment @@ -120,7 +121,7 @@ docs-linkcheckbroken: bin/python docs-news ## Run linkcheck and show only broke .PHONY: docs-vale docs-vale: bin/python docs-news ## Install (once) and run Vale style, grammar, and spell checks bin/vale sync - bin/vale --no-wrap $(VALEFILES) + bin/vale --no-wrap $(VALEOPTS) $(VALEFILES) @echo @echo "Vale is finished; look for any errors in the above output." diff --git a/apps/nextjs/.eslintrc.js b/apps/nextjs/.eslintrc.js deleted file mode 100644 index d0e37670c7..0000000000 --- a/apps/nextjs/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('eslint').Linter.Config} */ -module.exports = { - extends: 'next/core-web-vitals', - ignorePatterns: ['.next/**', 'dist/**', 'node_modules/**'], - settings: { - next: { - rootDir: 'apps/nextjs/', - }, - }, -}; diff --git a/apps/nextjs/.eslintrc.json b/apps/nextjs/.eslintrc.json new file mode 100644 index 0000000000..035e32e888 --- /dev/null +++ b/apps/nextjs/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"], + "ignorePatterns": [".next/**", "dist/**", "node_modules/**"], + "settings": { + "next": { + "rootDir": "apps/nextjs/" + } + } +} diff --git a/apps/nextjs/.gitignore b/apps/nextjs/.gitignore index e5671ba458..29b159ef05 100644 --- a/apps/nextjs/.gitignore +++ b/apps/nextjs/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +.yarn/install-state.gz # testing /coverage diff --git a/apps/nextjs/LICENSE b/apps/nextjs/LICENSE index c0af2b1b65..f6a5160bd5 100644 --- a/apps/nextjs/LICENSE +++ b/apps/nextjs/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Plone Foundation +Copyright (c) 2024 Plone Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/apps/nextjs/README.md b/apps/nextjs/README.md index 88a23215b6..4660a9939a 100644 --- a/apps/nextjs/README.md +++ b/apps/nextjs/README.md @@ -1,10 +1,16 @@ # Plone on Next.js -This is a proof of concept of a [Next.js](https://nextjs.org) app, using the app router and the `@plone/client` and `@plone/components` library. This is intended to serve as both a playground for the development of both packages and as demo of Plone using Next.js. +This is a proof of concept of a [Next.js](https://nextjs.org) app, using the app router and the `@plone/client` and `@plone/components` library. +This is intended to serve as both a playground for the development of both packages and as a demo of Plone using Next.js. + +> [!WARNING] +> This package or app is experimental. +> The community offers no support whatsoever for it. +> Breaking changes may occur without notice. ## Development -To start, from the root of the monorepo: +To start, from the root of the monorepo, issue the following commands in a shell session. ```shell pnpm install @@ -20,65 +26,93 @@ make backend-docker-start ## Deployment at Vercel - -We introduce an environment variable `API_SERVER_URL`. -You need to create this environment variable in the Vercel deployment's control panel, specifying the URL where your backend API server is deployed, and the route where the API is located, as shown. +For deploying your app at Vercel, you need to create the environment variable `API_SERVER_URL` in Vercel's deployment control panel, specifying the URL where your backend API server is deployed, and the route where the API is located, as shown. ```shell API_SERVER_URL=https://my-server-DNS-name.tld/api ``` +For production deployments, you will need to force the deployment URL, otherwise you will have issues with CORS. +To do so, set another environment variable for the production URL, `NEXT_PRODUCTION_URL`. +This URL needs to be scheme-less, without `http` or `https`, and consist only of the domain name: + +```shell +NEXT_PRODUCTION_URL=my-nextjs-production-DNS-name.tld +``` + ### Application rewrite configuragtion -To avoid issues with CORS and maintain the server counterpart private, our Next.js app should have a rewrite, configured as follows: +To avoid issues with CORS and maintain the server counterpart private, your Next.js app should have a rewrite, configured as follows: ```jsx const nextConfig = { // Rewrite to the backend to avoid CORS async rewrites() { - const apiServerURL = - process.env.API_SERVER_URL || - 'http://localhost:8080/Plone/%2B%2Bapi%2B%2B'; + let apiServerURL, vhmRewriteRule; + if ( + process.env.API_SERVER_URL && + (process.env.NEXT_PRODUCTION_URL || process.env.NEXT_PUBLIC_VERCEL_URL) + ) { + // We are in Vercel + apiServerURL = process.env.API_SERVER_URL; + vhmRewriteRule = `/VirtualHostBase/https/${ + process.env.NEXT_PRODUCTION_URL + ? // We are in the production deployment + process.env.NEXT_PRODUCTION_URL + : // We are in the preview deployment + process.env.NEXT_PUBLIC_VERCEL_URL + }%3A443/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot`; + } else if (process.env.API_SERVER_URL) { + // We are in development + apiServerURL = process.env.API_SERVER_URL; + vhmRewriteRule = + '/VirtualHostBase/http/localhost%3A3000/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot'; + } else { + // We are in development and the API_SERVER_URL is not set, so we use a local backend + apiServerURL = 'http://localhost:8080'; + vhmRewriteRule = + '/VirtualHostBase/http/localhost%3A3000/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot'; + } return [ { source: '/\\+\\+api\\+\\+/:slug*', destination: - `${apiServerURL}/VirtualHostBase/https/${process.env.NEXT_PUBLIC_VERCEL_URL}%3A443/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot/:slug*`, + `${apiServerURL}${vhmRewriteRule}/:slug*`, }, ]; }, }; ``` -Plone Client uses the `++api++` prefix as default, so we should create a redirect in our app pointing to the API server, but using Plone's traditional virtual host management configuration. +Plone Client uses the `++api++` prefix as default, so you should create a redirect in your app pointing to the API server, but using Plone's traditional virtual host management configuration. -Next.js rewrites are picky on the `destination` field, because its rewrite library does not support URLs with regular expression operators. -Therefore, we can't use the usual `++api++` route for the rewrite. -This will allow us to infer the current server URL—even in deployed branches and pull requests—without touching the rewrite rules. -We will fallback to configure a `api` route in our reverse proxy of choice. +Next.js rewrites are picky with the `destination` field, because its rewrite library does not support URLs with regular expression operators. +Therefore, you can't use the usual `++api++` route for the rewrite. +This will allow you to infer the current server URL—even in deployed branches and pull requests—without touching the rewrite rules. +You will fallback to configure a `api` route in your reverse proxy of choice. ### Plone backend You have to deploy the Plone backend elsewhere, since Vercel is serverless oriented. -We need to set up the rewrite rule in Next.js's `rewrite` feature as shown in the previous section. +You need to set up the rewrite rule in Next.js's `rewrite` feature as shown in the previous section. -We will fallback to configure an `api` route in our reverse proxy of choice. +You will fallback to configure an `api` route in your reverse proxy of choice. -For example, if we use `traefik`: +For example, if you use `traefik`: ```yaml - ## VHM rewrite /api/ (Plone Next.js) - - "traefik.http.middlewares.mw-backend-vhm-api.replacepathregex.regex=^/api($$|/.*)" - ## We remove the incoming /api and just use the path - - "traefik.http.middlewares.mw-backend-vhm-api.replacepathregex.replacement=$$1" - - ## /api router - - traefik.http.routers.rt-backend-api.rule=Host(`my_server_DNS_name`) && PathPrefix(`/api`) - - traefik.http.routers.rt-backend-api.entrypoints=https - - traefik.http.routers.rt-backend-api.tls=true - - traefik.http.routers.rt-backend-api.service=svc-backend - - traefik.http.routers.rt-backend-api.middlewares=gzip,mw-backend-vhm-api +## VHM rewrite /api/ (Plone Next.js) +- "traefik.http.middlewares.mw-backend-vhm-api.replacepathregex.regex=^/api($$|/.*)" +## We remove the incoming /api and just use the path +- "traefik.http.middlewares.mw-backend-vhm-api.replacepathregex.replacement=$$1" + +## /api router +- traefik.http.routers.rt-backend-api.rule=Host(`my_server_DNS_name`) && PathPrefix(`/api`) +- traefik.http.routers.rt-backend-api.entrypoints=https +- traefik.http.routers.rt-backend-api.tls=true +- traefik.http.routers.rt-backend-api.service=svc-backend +- traefik.http.routers.rt-backend-api.middlewares=gzip,mw-backend-vhm-api ``` ## About this app @@ -95,16 +129,18 @@ pnpm dev Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +You can start editing the page by modifying `app/page.tsx`. +The page auto-updates as you edit the file. ## Learn More To learn more about Next.js, take a look at the following resources: -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and its API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/). +Your feedback and contributions are welcome! ## Deploy on Vercel diff --git a/apps/nextjs/next.config.js b/apps/nextjs/next.config.mjs similarity index 53% rename from apps/nextjs/next.config.js rename to apps/nextjs/next.config.mjs index ae8d5d1813..2bc72a90f0 100644 --- a/apps/nextjs/next.config.js +++ b/apps/nextjs/next.config.mjs @@ -1,7 +1,10 @@ -const path = require('path'); +// import path from 'path'; /** @type {import('next').NextConfig} */ const nextConfig = { + typescript: { + ignoreBuildErrors: true, + }, // sassOptions: { // includePaths: [path.join(__dirname, 'src/lib/components/src/styles')], // }, @@ -18,17 +21,26 @@ const nextConfig = { // Rewrite to the backend to avoid CORS async rewrites() { let apiServerURL, vhmRewriteRule; - if (process.env.API_SERVER_URL) { - apiServerURL = process.env.API_SERVER_URL; - vhmRewriteRule = `/VirtualHostBase/https/${process.env.NEXT_PUBLIC_VERCEL_URL}%3A443/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot`; - } else if ( + if ( process.env.API_SERVER_URL && - !process.env.NEXT_PUBLIC_VERCEL_URL + (process.env.NEXT_PRODUCTION_URL || process.env.NEXT_PUBLIC_VERCEL_URL) ) { - throw new Error( - 'API_SERVER_URL set and NEXT_PUBLIC_VERCEL_URL not present.', - ); + // We are in Vercel + apiServerURL = process.env.API_SERVER_URL; + vhmRewriteRule = `/VirtualHostBase/https/${ + process.env.NEXT_PRODUCTION_URL + ? // We are in the production deployment + process.env.NEXT_PRODUCTION_URL + : // We are in the preview deployment + process.env.NEXT_PUBLIC_VERCEL_URL + }%3A443/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot`; + } else if (process.env.API_SERVER_URL) { + // We are in development + apiServerURL = process.env.API_SERVER_URL; + vhmRewriteRule = + '/VirtualHostBase/http/localhost%3A3000/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot'; } else { + // We are in development and the API_SERVER_URL is not set, so we use a local backend apiServerURL = 'http://localhost:8080'; vhmRewriteRule = '/VirtualHostBase/http/localhost%3A3000/Plone/%2B%2Bapi%2B%2B/VirtualHostRoot'; @@ -46,4 +58,4 @@ const nextConfig = { }, }; -module.exports = nextConfig; +export default nextConfig; diff --git a/apps/nextjs/package.json b/apps/nextjs/package.json index 6d4a71dc0f..8b935ff8bb 100644 --- a/apps/nextjs/package.json +++ b/apps/nextjs/package.json @@ -13,21 +13,22 @@ "@plone/blocks": "workspace: *", "@plone/client": "workspace: *", "@plone/components": "workspace: *", - "@plone/registry": "workspace: *", "@plone/providers": "workspace: *", - "@tanstack/react-query": "^5.37.1", - "next": "14.2.2", + "@plone/registry": "workspace: *", + "@tanstack/react-query": "^5.59.0", + "next": "14.2.14", "react": "^18", - "react-aria-components": "^1.1.1", + "react-aria-components": "^1.4.0", "react-dom": "^18" }, "devDependencies": { - "@tanstack/react-query-devtools": "^5.37.1", + "@plone/types": "workspace: *", + "@tanstack/react-query-devtools": "^5.59.0", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", - "eslint-config-next": "14.2.2", - "typescript": "^5.4.5" + "eslint-config-next": "14.2.14", + "typescript": "^5.6.3" } } diff --git a/apps/nextjs/src/app/Providers.tsx b/apps/nextjs/src/app/Providers.tsx index 27b47036f6..e1c0fa9295 100644 --- a/apps/nextjs/src/app/Providers.tsx +++ b/apps/nextjs/src/app/Providers.tsx @@ -1,15 +1,32 @@ 'use client'; import React from 'react'; -import { useRouter } from 'next/navigation'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { PloneClientProvider } from '@plone/providers'; +import { + useRouter, + usePathname, + useSearchParams, + useParams, +} from 'next/navigation'; +import { QueryClient } from '@tanstack/react-query'; +import { PloneProvider } from '@plone/providers'; import PloneClient from '@plone/client'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { RouterProvider } from 'react-aria-components'; -import { FlattenToAppURLProvider } from '@plone/components'; import { flattenToAppURL } from './utils'; import config from './config'; +// Custom hook to unify the location object between NextJS and Plone +function useLocation() { + const pathname = usePathname(); + const search = useSearchParams(); + + return { + pathname, + search, + searchStr: '', + hash: (typeof window !== 'undefined' && window.location.hash) || '', + href: (typeof window !== 'undefined' && window.location.href) || '', + }; +} + const Providers: React.FC<{ children?: React.ReactNode; }> = ({ children }) => { @@ -36,19 +53,25 @@ const Providers: React.FC<{ }), ); - let router = useRouter(); + const router = useRouter(); return ( - - - - - {children} - - - - - + { + router.push(to); + }} + useParams={useParams} + useHref={(to) => flattenToAppURL(to)} + flattenToAppURL={flattenToAppURL} + > + {children} + + ); }; diff --git a/apps/nextjs/src/app/config.ts b/apps/nextjs/src/app/config.ts index 47bad754da..8d0b440630 100644 --- a/apps/nextjs/src/app/config.ts +++ b/apps/nextjs/src/app/config.ts @@ -1,12 +1,30 @@ -const settings = { - apiPath: process.env.NEXT_PUBLIC_VERCEL_URL - ? // Vercel does not prepend the schema to the NEXT_PUBLIC_VERCEL_URL automatic env var - `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` - : 'http://localhost:3000', -}; +import config from '@plone/registry'; +import type { ConfigType } from '@plone/registry'; +import { slate } from '@plone/blocks'; +import { blocksConfig } from '@plone/blocks'; -const config = { - settings, +const settings: Partial = { + slate, }; +if (process.env.NEXT_PUBLIC_VERCEL_URL) { + // This app is at Vercel + if (process.env.NEXT_PRODUCTION_URL) { + // This app is in a production deployment, so set the apiPath to the production URL + settings.apiPath = process.env.NEXT_PRODUCTION_URL; + } else { + // This app is in a preview deployment, so set the apiPath to the Vercel URL + settings.apiPath = `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`; + } +} else { + // This app is in development, so set the apiPath to localhost + settings.apiPath = 'http://localhost:3000/'; +} + +// @ts-expect-error Improve typings +config.set('settings', settings); + +// @ts-expect-error Improve typings +config.set('blocks', { blocksConfig }); + export default config; diff --git a/apps/nextjs/src/app/content.tsx b/apps/nextjs/src/app/content.tsx index 92e757e7da..84e2b244b7 100644 --- a/apps/nextjs/src/app/content.tsx +++ b/apps/nextjs/src/app/content.tsx @@ -1,46 +1,30 @@ 'use client'; - import { useQuery } from '@tanstack/react-query'; import { usePathname } from 'next/navigation'; -import Link from 'next/link'; -import { flattenToAppURL } from './utils'; import { usePloneClient } from '@plone/providers'; -import { Breadcrumbs } from '@plone/components'; +import { Breadcrumbs, RenderBlocks } from '@plone/components'; +import config from '@plone/registry'; + import '@plone/components/dist/basic.css'; export default function Content() { const { getContentQuery } = usePloneClient(); const pathname = usePathname(); - const { data, isLoading } = useQuery(getContentQuery({ path: pathname })); + const { data } = useQuery(getContentQuery({ path: pathname })); if (data) { return (
-

{data.title}

- {/* */} -
    - {data?.['@components']?.navigation?.items?.map((item) => ( -
  • - - {flattenToAppURL(item['@id'])} - -
  • - ))} -
-
-
{JSON.stringify(data, null, 2)}
-
+
); } diff --git a/apps/nextjs/src/app/layout.tsx b/apps/nextjs/src/app/layout.tsx index 3c118fb9fa..8d254759a2 100644 --- a/apps/nextjs/src/app/layout.tsx +++ b/apps/nextjs/src/app/layout.tsx @@ -5,7 +5,7 @@ import Providers from './Providers'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { - title: 'NextJS-powered Plone', + title: 'Next.js app powered by Plone', description: '', }; diff --git a/apps/nextjs/src/middleware.ts b/apps/nextjs/src/middleware.ts index 59c2d7b5d4..736d67530c 100644 --- a/apps/nextjs/src/middleware.ts +++ b/apps/nextjs/src/middleware.ts @@ -2,10 +2,7 @@ import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { - // Clone the request headers and set a new header `x-hello-from-middleware1` const requestHeaders = new Headers(request.headers); - // console.log(request.nextUrl.pathname); - // requestHeaders.set('x-hello-from-middleware1', 'hello'); // You can also set request headers in NextResponse.rewrite const response = NextResponse.next({ diff --git a/apps/nextjs/tsconfig.json b/apps/nextjs/tsconfig.json index 33bcb0c4f4..4b7888fa43 100644 --- a/apps/nextjs/tsconfig.json +++ b/apps/nextjs/tsconfig.json @@ -1,38 +1,34 @@ { - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ] - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "**/*.js", - "**/*.jx", - ".next/types/**/*.ts" + "compilerOptions": { + "forceConsistentCasingInFileNames": true, + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } ], - "exclude": [ - "node_modules", - ] + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + ".next/types/**/*.ts" + ], + "exclude": ["node_modules"] } diff --git a/apps/plone/.editorconfig b/apps/plone/.editorconfig deleted file mode 100644 index 6c43b88c57..0000000000 --- a/apps/plone/.editorconfig +++ /dev/null @@ -1,34 +0,0 @@ -# EditorConfig Configurtaion file, for more details see: -# http://EditorConfig.org -# EditorConfig is a convention description, that could be interpreted -# by multiple editors to enforce common coding conventions for specific -# file types - -# top-most EditorConfig file: -# Will ignore other EditorConfig files in Home directory or upper tree level. -root = true - - -[*] # For All Files -# Unix-style newlines with a newline ending every file -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -# Set default charset -charset = utf-8 -# Indent style default -indent_style = space - -[*.{py,cfg,ini}] -# 4 space indentation -indent_size = 4 - -[*.{html,dtml,pt,zpt,xml,zcml,js,jsx,json,less,css,yaml,yml}] -# 2 space indentation -indent_size = 2 - -[{Makefile,.gitmodules}] -# Tab indentation (no size specified, but view as 4 spaces) -indent_style = tab -indent_size = unset -tab_width = unset diff --git a/apps/plone/.eslintrc.js b/apps/plone/.eslintrc.js deleted file mode 100644 index 6148e2e62f..0000000000 --- a/apps/plone/.eslintrc.js +++ /dev/null @@ -1,63 +0,0 @@ -const fs = require('fs'); -const projectRootPath = __dirname; -const AddonConfigurationRegistry = require('@plone/registry/src/addon-registry'); - -let voltoPath = './node_modules/@plone/volto'; - -let configFile; -if (fs.existsSync(`${projectRootPath}/tsconfig.json`)) - configFile = `${projectRootPath}/tsconfig.json`; -else if (fs.existsSync(`${projectRootPath}/jsconfig.json`)) - configFile = `${projectRootPath}/jsconfig.json`; - -if (configFile) { - const jsConfig = require(configFile).compilerOptions; - const pathsConfig = jsConfig.paths; - if (pathsConfig['@plone/volto']) - voltoPath = `./${jsConfig.baseUrl}/${pathsConfig['@plone/volto'][0]}`; -} - -const reg = new AddonConfigurationRegistry(__dirname); - -// Extends ESlint configuration for adding the aliases to `src` directories in Volto addons -const addonAliases = Object.keys(reg.packages).map((o) => [ - o, - reg.packages[o].modulePath, -]); - -const addonExtenders = reg.getEslintExtenders().map((m) => require(m)); - -const defaultConfig = { - extends: `${voltoPath}/.eslintrc`, - ignorePatterns: [ - // '.storybook/**/*', - 'src/addons/**/node_modules', - 'src/addons/**/cypress', - 'src/addons/**/build', - '!src/addons/volto-volto-project', - ], - settings: { - 'import/resolver': { - alias: { - map: [ - ['@plone/volto', '@plone/volto/src'], - ['@plone/volto-slate', '@plone/volto-slate/src'], - ...addonAliases, - ['@root', `${__dirname}/src`], - ['~', `${__dirname}/src`], - ], - extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], - }, - 'babel-plugin-root-import': { - rootPathSuffix: 'src', - }, - }, - }, -}; - -const config = addonExtenders.reduce( - (acc, extender) => extender.modify(acc), - defaultConfig, -); - -module.exports = config; diff --git a/apps/plone/.gitignore b/apps/plone/.gitignore deleted file mode 100644 index a8b3ab728b..0000000000 --- a/apps/plone/.gitignore +++ /dev/null @@ -1,82 +0,0 @@ -.vscode/ -logs -*.log -npm-debug.log* -.DS_Store - -coverage - -# Node -node_modules -coverage -jsdoc -webpack-assets.json -webpack-stats.json -npm-debug.log -dist -junit.xml -eslint.xml -yarn-error.log -build - -# Other -.DS_Store -.idea -lighthouse-report.html -.vscode/ -.#* -*~ - -# Python -/bin/ -/lib/ -.Python -include -pip-selfcheck.json -pyvenv.cfg -share - -# locales -locales/*.json - -# Tests -/tests/bin -/tests/develop-eggs -/tests/parts -/tests/.installed.cfg -*.pyc -geckodriver.log -log.html -output.xml -report.html -selenium-screenshot-*.png -/selenium/ -cypress/videos/ -cypress/screenshots - -# Local environment setup -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -# generic -data -omelette - -# build -public/critical.css -src/addons/* -/cache - -# yarn 3 -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions - -!src/addons/volto-volto-project diff --git a/apps/plone/.prettierignore b/apps/plone/.prettierignore deleted file mode 100644 index 84cca9725b..0000000000 --- a/apps/plone/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -src/**/CHANGELOG.md -src/**/README.md -!src/addons/volto-volto-project \ No newline at end of file diff --git a/apps/plone/.storybook/main.js b/apps/plone/.storybook/main.js deleted file mode 100644 index 0455b194c9..0000000000 --- a/apps/plone/.storybook/main.js +++ /dev/null @@ -1,177 +0,0 @@ -const webpack = require('webpack'); -const fs = require('fs'); -const path = require('path'); - -const projectRootPath = path.resolve('.'); -const lessPlugin = require('@plone/volto/webpack-plugins/webpack-less-plugin'); -const scssPlugin = require('razzle-plugin-scss'); - -const createConfig = require('../node_modules/razzle/config/createConfigAsync.js'); -const razzleConfig = require(path.join(projectRootPath, 'razzle.config.js')); - -const SVGLOADER = { - test: /icons\/.*\.svg$/, - use: [ - { - loader: 'svg-loader', - }, - { - loader: 'svgo-loader', - options: { - plugins: [ - { - name: 'preset-default', - params: { - overrides: { - convertPathData: false, - removeViewBox: false, - }, - }, - }, - 'removeTitle', - 'removeUselessStrokeAndFill', - ], - }, - }, - ], -}; - -const defaultRazzleOptions = { - verbose: false, - debug: {}, - buildType: 'iso', - cssPrefix: 'static/css', - jsPrefix: 'static/js', - enableSourceMaps: true, - enableReactRefresh: true, - enableTargetBabelrc: false, - enableBabelCache: true, - forceRuntimeEnvVars: [], - mediaPrefix: 'static/media', - staticCssInDev: false, - emitOnErrors: false, - disableWebpackbar: false, - browserslist: [ - '>1%', - 'last 4 versions', - 'Firefox ESR', - 'not ie 11', - 'not dead', - ], -}; - -module.exports = { - core: { - builder: 'webpack5', - }, - stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - addons: [ - '@storybook/addon-links', - '@storybook/addon-essentials', - // '@storybook/preset-scss', - ], - webpackFinal: async (config, { configType }) => { - // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' - // You can change the configuration based on that. - // 'PRODUCTION' is used when building the static version of storybook. - - // Make whatever fine-grained changes you need - let baseConfig; - baseConfig = await createConfig( - 'web', - 'dev', - { - // clearConsole: false, - modifyWebpackConfig: razzleConfig.modifyWebpackConfig, - plugins: razzleConfig.plugins, - }, - webpack, - false, - undefined, - [], - defaultRazzleOptions, - ); - const AddonConfigurationRegistry = require('@plone/volto/addon-registry'); - - const registry = new AddonConfigurationRegistry(projectRootPath); - - config = lessPlugin({ registry }).modifyWebpackConfig({ - env: { target: 'web', dev: 'dev' }, - webpackConfig: config, - webpackObject: webpack, - options: {}, - }); - - config = scssPlugin.modifyWebpackConfig({ - env: { target: 'web', dev: 'dev' }, - webpackConfig: config, - webpackObject: webpack, - options: { razzleOptions: {} }, - }); - - // Put the SVG loader on top and prevent the asset/resource rule - // from processing the app's SVGs - config.module.rules.unshift(SVGLOADER); - const fileLoaderRule = config.module.rules.find((rule) => - rule.test.test('.svg'), - ); - fileLoaderRule.exclude = /icons\/.*\.svg$/; - - config.plugins.unshift( - new webpack.DefinePlugin({ - __DEVELOPMENT__: true, - __CLIENT__: true, - __SERVER__: false, - }), - ); - - const resultConfig = { - ...config, - resolve: { - ...config.resolve, - alias: { ...config.resolve.alias, ...baseConfig.resolve.alias }, - fallback: { ...config.resolve.fallback, zlib: false }, - }, - }; - - // Addons have to be loaded with babel - const addonPaths = registry.addonNames.map((addon) => - fs.realpathSync(registry.packages[addon].modulePath), - ); - resultConfig.module.rules[1].exclude = (input) => - // exclude every input from node_modules except from @plone/volto - /node_modules\/(?!(@plone\/volto)\/)/.test(input) && - // If input is in an addon, DON'T exclude it - !addonPaths.some((p) => input.includes(p)); - - const addonExtenders = registry.getAddonExtenders().map((m) => require(m)); - - const extendedConfig = addonExtenders.reduce( - (acc, extender) => - extender.modify(acc, { target: 'web', dev: 'dev' }, config), - resultConfig, - ); - - // Note: we don't actually support razzle plugins, which are also a feature - // of the razzle.extend.js addons file. Those features are probably - // provided in a different manner by Storybook plugins (for example scss - // loaders). - - return extendedConfig; - }, - babel: async (options) => { - return { - ...options, - plugins: [ - ...options.plugins, - [ - './node_modules/babel-plugin-root-import/build/index.js', - { - rootPathSuffix: './src', - }, - ], - ], - // any extra options you want to set - }; - }, -}; diff --git a/apps/plone/.storybook/manager.js b/apps/plone/.storybook/manager.js deleted file mode 100644 index cda31aa1fe..0000000000 --- a/apps/plone/.storybook/manager.js +++ /dev/null @@ -1,15 +0,0 @@ -import { addons } from '@storybook/addons'; - -addons.setConfig({ - isFullscreen: false, - showNav: true, - showPanel: true, - panelPosition: 'bottom', - sidebarAnimations: true, - enableShortcuts: true, - isToolshown: true, - theme: undefined, - selectedPanel: undefined, - initialActive: 'sidebar', - showRoots: true, -}); diff --git a/apps/plone/.storybook/preview.js b/apps/plone/.storybook/preview.js deleted file mode 100644 index bcbfccb212..0000000000 --- a/apps/plone/.storybook/preview.js +++ /dev/null @@ -1,28 +0,0 @@ -import '@plone/volto/config'; // This is the bootstrap for the global config - client side -import React from 'react'; -import { StaticRouter } from 'react-router-dom'; -import { IntlProvider } from 'react-intl'; -// eslint-disable-next-line import/no-unresolved -import enMessages from '@root/../locales/en.json'; - -import '@root/theme'; - -export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, -}; - -export const decorators = [ - (Story) => ( - - - - - - ), -]; diff --git a/apps/plone/Makefile b/apps/plone/Makefile deleted file mode 100644 index 4a5a606c1f..0000000000 --- a/apps/plone/Makefile +++ /dev/null @@ -1,106 +0,0 @@ -### Defensive settings for make: -# https://tech.davis-hansson.com/p/make/ -SHELL:=bash -.ONESHELL: -.SHELLFLAGS:=-eu -o pipefail -c -.SILENT: -.DELETE_ON_ERROR: -MAKEFLAGS+=--warn-undefined-variables -MAKEFLAGS+=--no-builtin-rules - -# Update the versions depending on your project requirements | Last Updated 2023-03-02 -DOCKER_IMAGE=plone/server-dev:6.0.6 -DOCKER_IMAGE_ACCEPTANCE=plone/server-acceptance:6.0.6 -KGS= -NODEBIN = ./node_modules/.bin - -# Plone 5 legacy -DOCKER_IMAGE5=plone/plone-backend:5.2.12 -KGS5=plone.restapi==8.43.3 plone.volto==4.1.0 plone.rest==3.0.1 -TESTING_ADDONS=plone.app.robotframework==2.0.0 plone.app.testing==7.0.0 - -# Project settings - -DIR=$(shell basename $$(pwd)) - -# Recipe snippets for reuse - -# We like colors -# From: https://coderwall.com/p/izxssa/colored-makefile-for-golang-projects -RED=`tput setaf 1` -GREEN=`tput setaf 2` -RESET=`tput sgr0` -YELLOW=`tput setaf 3` - - -# Top-level targets -.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-backend-docker -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: 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) - if [ -f $$(pwd)/jsconfig.json ]; then npx -p mrs-developer missdev --config=jsconfig.json --output=addons --fetch-https; fi - if [ ! -f $$(pwd)/jsconfig.json ]; then npx -p mrs-developer missdev --output=addons --fetch-https; fi - -.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 - -.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 -p 55001:55001 $(DOCKER_IMAGE_ACCEPTANCE) - ## 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://127.0.0.1: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://127.0.0.1:55001/plone "make start-test-acceptance-frontend" http://127.0.0.1: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://127.0.0.1:55001/plone "make start-test-acceptance-frontend" http://127.0.0.1:3000 "make test-acceptance-addon-headless" diff --git a/apps/plone/README.md b/apps/plone/README.md deleted file mode 100644 index da034e8ad3..0000000000 --- a/apps/plone/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# README.md - -This README guides you for development with Volto and Plone. - - -## Documentation - -[Volto Hands-On](https://training.plone.org/voltohandson/index.html) is a training on how to create your own website. - - -## Quick Start - -Below is a list of useful commands. - -### `make install` - -Installs and checks out 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. -You can view your application at `http://localhost:3000`. - -The page will reload if you make edits. - - -### `yarn build` - -Builds the app for production to the `build` folder. - -The build is minified and the filenames include the hashes. -Your app is ready to be deployed! - - -### `yarn start:prod` - -Runs the compiled app in production. - -You can view your application at `http://localhost:3000`. - - -### `yarn test` - -Runs the test watcher (Jest) in an interactive mode. -By default, runs tests related to files changed since the last commit. - - -### `yarn i18n` - -Runs the test i18n runner, which extracts all the translation strings and generates the needed files. - -### mrs-developer - -[mrs-developer](https://github.com/collective/mrs-developer) is a great tool for developing multiple packages at the same time. - -mrs-developer should work with this project by running the configured shortcut script: - -```bash -make develop -``` - -Volto's latest Razzle configuration will honor your `tsconfig.json` or `jsconfig.json` file for any customizations. - -In case you don't want (or can't) install mrs-developer globally, you can install it in this project by running: - -```bash -yarn add -W mrs-developer -``` - - -## Acceptance tests - -To run the project acceptance tests while developing using Cypress as the test runner, there are some `Makefile` commands in place in the repository root. - -Run them in order: - -- `start-test-acceptance-server`: Start the server fixture in Docker (previous build required). -- `start-test-acceptance-frontend`: Start the Core Acceptance Frontend Fixture in development mode. -- `test-acceptance`: Start the Core Cypress Acceptance Tests in development mode. -- `full-test-acceptance`: Start the entire suite (backend + frontend + headless tests) using Cypress Acceptance Tests in headless (CI) mode. diff --git a/apps/plone/babel.config.js b/apps/plone/babel.config.js deleted file mode 100644 index a900a7555e..0000000000 --- a/apps/plone/babel.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@plone/volto/babel'); diff --git a/apps/plone/cypress.config.js b/apps/plone/cypress.config.js deleted file mode 100644 index 08d55e62ba..0000000000 --- a/apps/plone/cypress.config.js +++ /dev/null @@ -1,8 +0,0 @@ -const { defineConfig } = require('cypress'); - -module.exports = defineConfig({ - e2e: { - baseUrl: 'http://localhost:3000', - specPattern: 'cypress/tests/**/*.cy.{js,jsx}', - }, -}); diff --git a/apps/plone/cypress/fixtures/broccoli.jpg b/apps/plone/cypress/fixtures/broccoli.jpg deleted file mode 100644 index 456706d662..0000000000 Binary files a/apps/plone/cypress/fixtures/broccoli.jpg and /dev/null differ diff --git a/apps/plone/cypress/fixtures/example.json b/apps/plone/cypress/fixtures/example.json deleted file mode 100644 index 02e4254378..0000000000 --- a/apps/plone/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/apps/plone/cypress/fixtures/file.pdf b/apps/plone/cypress/fixtures/file.pdf deleted file mode 100644 index ef9c798194..0000000000 Binary files a/apps/plone/cypress/fixtures/file.pdf and /dev/null differ diff --git a/apps/plone/cypress/fixtures/halfdome2022.jpg b/apps/plone/cypress/fixtures/halfdome2022.jpg deleted file mode 100644 index 58c9411ca2..0000000000 Binary files a/apps/plone/cypress/fixtures/halfdome2022.jpg and /dev/null differ diff --git a/apps/plone/cypress/fixtures/image.png b/apps/plone/cypress/fixtures/image.png deleted file mode 100644 index 4c109bab8d..0000000000 Binary files a/apps/plone/cypress/fixtures/image.png and /dev/null differ diff --git a/apps/plone/cypress/plugins/index.js b/apps/plone/cypress/plugins/index.js deleted file mode 100644 index dffed2532f..0000000000 --- a/apps/plone/cypress/plugins/index.js +++ /dev/null @@ -1,17 +0,0 @@ -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -module.exports = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config -}; diff --git a/apps/plone/cypress/support/commands.js b/apps/plone/cypress/support/commands.js deleted file mode 100644 index 03fe48c752..0000000000 --- a/apps/plone/cypress/support/commands.js +++ /dev/null @@ -1,9 +0,0 @@ -import '@plone/volto/cypress/add-commands'; - -// Set PLONE_SITE_ID and PLONE_API_URL as cypress environment variables -// if testing without using localhost or a site id of `plone`. - -// --- CUSTOM COMMANDS ------------------------------------------------------------- -Cypress.Commands.add('custom_command', () => { - // Custom code here... -}); diff --git a/apps/plone/cypress/support/e2e.js b/apps/plone/cypress/support/e2e.js deleted file mode 100644 index 48912f7f19..0000000000 --- a/apps/plone/cypress/support/e2e.js +++ /dev/null @@ -1,14 +0,0 @@ -import 'cypress-axe'; -import 'cypress-file-upload'; -import './commands'; -import { setup, teardown } from './reset-fixture'; - -beforeEach(function () { - cy.log('Setting up API fixture'); - setup(); -}); - -afterEach(function () { - cy.log('Tearing down API fixture'); - teardown(); -}); diff --git a/apps/plone/cypress/support/reset-fixture.js b/apps/plone/cypress/support/reset-fixture.js deleted file mode 100644 index 4e2b4372d5..0000000000 --- a/apps/plone/cypress/support/reset-fixture.js +++ /dev/null @@ -1,43 +0,0 @@ -function setup() { - 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() { - 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')); -} - -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/apps/plone/locales/de/LC_MESSAGES/volto.po b/apps/plone/locales/de/LC_MESSAGES/volto.po deleted file mode 100644 index 49d2e20662..0000000000 --- a/apps/plone/locales/de/LC_MESSAGES/volto.po +++ /dev/null @@ -1,20 +0,0 @@ -# Translation of volto.pot to German -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"PO-Revision-Date: 2016-10-22 16:41-0500\n" -"Last-Translator: German \n" -"Language: de\n" -"Language-Team: German \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: de\n" -"Language-Name: Deutsch\n" -"Preferred-Encodings: utf-8 latin1\n" -"X-Is-Fallback-For: de-at de-li de-lu de-ch de-de\n" - - diff --git a/apps/plone/locales/en/LC_MESSAGES/volto.po b/apps/plone/locales/en/LC_MESSAGES/volto.po deleted file mode 100644 index 12b9a47628..0000000000 --- a/apps/plone/locales/en/LC_MESSAGES/volto.po +++ /dev/null @@ -1,14 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language: \n" -"Language-Team: \n" -"Content-Type: \n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - - diff --git a/apps/plone/locales/es/LC_MESSAGES/volto.po b/apps/plone/locales/es/LC_MESSAGES/volto.po deleted file mode 100644 index 784b64fb17..0000000000 --- a/apps/plone/locales/es/LC_MESSAGES/volto.po +++ /dev/null @@ -1,21 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-12-03 03:20-0400\n" -"PO-Revision-Date: 2019-12-12 06:07-0400\n" -"Last-Translator: Leonardo J. Caballero G. \n" -"Language: es\n" -"Language-Team: ES \n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"MIME-Version: 1.0\n" -"X-Generator: Virtaal 0.7.1\n" -"Language-Code: es\n" -"Language-Name: Español\n" -"Preferred-Encodings: utf-8\n" -"Domain: volto\n" -"X-Is-Fallback-For: es-ar es-bo es-cl es-co es-cr es-do es-ec es-es es-sv es-gt es-hn es-mx es-ni es-pa es-py es-pe es-pr es-us es-uy es-ve\n" - - diff --git a/apps/plone/locales/eu/LC_MESSAGES/volto.po b/apps/plone/locales/eu/LC_MESSAGES/volto.po deleted file mode 100644 index 984adca72e..0000000000 --- a/apps/plone/locales/eu/LC_MESSAGES/volto.po +++ /dev/null @@ -1,19 +0,0 @@ -# Translation of volto.pot to EU -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"PO-Revision-Date: 2016-10-22 16:41-0500\n" -"Last-Translator: Plone i18n \n" -"Language: eu\n" -"Language-Team: Plone i18n \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: eu\n" -"Language-Name: eu\n" -"Preferred-Encodings: utf-8 latin1\n" - - diff --git a/apps/plone/locales/fr/LC_MESSAGES/volto.po b/apps/plone/locales/fr/LC_MESSAGES/volto.po deleted file mode 100644 index f6050e4530..0000000000 --- a/apps/plone/locales/fr/LC_MESSAGES/volto.po +++ /dev/null @@ -1,20 +0,0 @@ -# Translation of volto.pot to French -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"PO-Revision-Date: 2016-10-22 16:41-0500\n" -"Last-Translator: Benoît Suttor \n" -"Language: fr\n" -"Language-Team: French \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: fr\n" -"Language-Name: French\n" -"Preferred-Encodings: utf-8 latin1\n" -"X-Is-Fallback-For: fr-be fr-ca fr-lu fr-mc fr-ch fr-fr\n" - - diff --git a/apps/plone/locales/it/LC_MESSAGES/volto.po b/apps/plone/locales/it/LC_MESSAGES/volto.po deleted file mode 100644 index 12b9a47628..0000000000 --- a/apps/plone/locales/it/LC_MESSAGES/volto.po +++ /dev/null @@ -1,14 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: \n" -"Last-Translator: \n" -"Language: \n" -"Language-Team: \n" -"Content-Type: \n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - - diff --git a/apps/plone/locales/ja/LC_MESSAGES/volto.po b/apps/plone/locales/ja/LC_MESSAGES/volto.po deleted file mode 100644 index a543785fbf..0000000000 --- a/apps/plone/locales/ja/LC_MESSAGES/volto.po +++ /dev/null @@ -1,19 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"PO-Revision-Date: 2019-11-15 12:06+0900\n" -"Last-Translator: Manabu TERADA \n" -"Language: ja\n" -"Language-Team: Plone Japanese 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: ja\n" -"Language-Name: Japanese\n" -"Preferred-Encodings: utf-8\n" -"X-Is-Fallback-For: ja-jp\n" - - diff --git a/apps/plone/locales/nl/LC_MESSAGES/volto.po b/apps/plone/locales/nl/LC_MESSAGES/volto.po deleted file mode 100644 index a9816b4cea..0000000000 --- a/apps/plone/locales/nl/LC_MESSAGES/volto.po +++ /dev/null @@ -1,18 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: PlonenPOT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: \n" -"PO-Revision-Date: 2017-02-28 08:52+0100\n" -"Last-Translator: Dutch \n" -"Language: nl\n" -"Language-Team: Dutch \n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"MIME-Version: 1.0\n" -"Language-Code: nl\n" -"Language-Name: Nederlands\n" -"Preferred-Encodings: utf-8\n" - - diff --git a/apps/plone/locales/pt/LC_MESSAGES/volto.po b/apps/plone/locales/pt/LC_MESSAGES/volto.po deleted file mode 100644 index 3713b40e7d..0000000000 --- a/apps/plone/locales/pt/LC_MESSAGES/volto.po +++ /dev/null @@ -1,19 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-12-17T20:38:19.520Z\n" -"PO-Revision-Date: \n" -"Last-Translator: Emanuel de Jesus , 2019\n" -"Language: pt\n" -"Language-Team: Portuguese (https://www.transifex.com/plone/teams/14552/pt/)\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"MIME-Version: 1.0\n" -"Domain: volto\n" -"Language-Code: pt\n" -"Language-Name: Portuguese\n" -"Preferred-Encodings: utf-8\n" - - diff --git a/apps/plone/locales/pt_BR/LC_MESSAGES/volto.po b/apps/plone/locales/pt_BR/LC_MESSAGES/volto.po deleted file mode 100644 index d788679fdc..0000000000 --- a/apps/plone/locales/pt_BR/LC_MESSAGES/volto.po +++ /dev/null @@ -1,18 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"PO-Revision-Date: 2017-02-28 08:52+0100\n" -"Last-Translator: Léu Almeida \n" -"Language: pt-BR\n" -"Language-Team: Plone i18n \n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"MIME-Version: 1.0\n" -"Language-Code: pt-br\n" -"Language-Name: Português do Brasil\n" -"Preferred-Encodings: utf-8\n" - - diff --git a/apps/plone/locales/ro/LC_MESSAGES/volto.po b/apps/plone/locales/ro/LC_MESSAGES/volto.po deleted file mode 100644 index 42c60f4d17..0000000000 --- a/apps/plone/locales/ro/LC_MESSAGES/volto.po +++ /dev/null @@ -1,18 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-04-27T19:30:59.079Z\n" -"PO-Revision-Date: 2016-10-22 16:41-0500\n" -"Last-Translator: Plone i18n \n" -"Language: ro\n" -"Language-Team: Plone i18n \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: ro\n" -"Language-Name: Romanian\n" -"Preferred-Encodings: utf-8 latin1\n" - - diff --git a/apps/plone/locales/volto.pot b/apps/plone/locales/volto.pot deleted file mode 100644 index 430c431165..0000000000 --- a/apps/plone/locales/volto.pot +++ /dev/null @@ -1,16 +0,0 @@ -msgid "" -msgstr "" -"Project-Id-Version: Plone\n" -"POT-Creation-Date: 2018-10-03T09:01:24.737Z\n" -"Last-Translator: Plone i18n \n" -"Language-Team: Plone i18n \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: en\n" -"Language-Name: English\n" -"Preferred-Encodings: utf-8\n" -"Domain: volto\n" - - diff --git a/apps/plone/mrs.developer.json b/apps/plone/mrs.developer.json deleted file mode 100644 index 502bb075b9..0000000000 --- a/apps/plone/mrs.developer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "volto-volto-project": { - "local": "addons/volto-volto-project/src" - } -} \ No newline at end of file diff --git a/apps/plone/package.json b/apps/plone/package.json deleted file mode 100644 index e87b8c95e3..0000000000 --- a/apps/plone/package.json +++ /dev/null @@ -1,361 +0,0 @@ -{ - "name": "plone", - "description": "Plone CMS Volto frontend project", - "license": "MIT", - "version": "1.0.0", - "scripts": { - "start": "razzle start", - "build": "razzle build --noninteractive", - "lint": "./node_modules/eslint/bin/eslint.js --max-warnings=0 'src/**/*.{js,jsx,ts,tsx}'", - "lint:fix": "./node_modules/eslint/bin/eslint.js --fix 'src/**/*.{js,jsx,ts,tsx}'", - "lint:ci": "./node_modules/eslint/bin/eslint.js --max-warnings=0 -f checkstyle 'src/**/*.{js,jsx,ts,tsx}' > eslint.xml", - "prettier": "./node_modules/.bin/prettier --single-quote --check 'src/**/*.{js,jsx,ts,tsx,css,scss}'", - "prettier:fix": "./node_modules/.bin/prettier --single-quote --write 'src/**/*.{js,jsx,ts,tsx,css,scss}'", - "prettier:ci": "./node_modules/.bin/prettier --single-quote --check 'src/**/*.{js,jsx,ts,tsx,css,scss}'", - "stylelint": "stylelint 'theme/**/*.{css,scss,less}' 'src/**/*.{css,scss,less}'", - "stylelint:overrides": "stylelint 'theme/**/*.overrides' 'src/**/*.overrides'", - "stylelint:fix": "yarn stylelint --fix && yarn stylelint:overrides --fix", - "test": "razzle test --passWithNoTests", - "typecheck": "tsc --project tsconfig.json --noEmit", - "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", - "storybook": "start-storybook -p 6006", - "build-storybook": "build-storybook" - }, - "workspaces": [ - "src/addons/volto-volto-project" - ], - "addons": [ - "volto-volto-project" - ], - "jest": { - "modulePathIgnorePatterns": [ - "api" - ], - "transform": { - "^.+\\.js(x)?$": "babel-jest", - "^.+\\.(png)$": "jest-file", - "^.+\\.(jpg)$": "jest-file", - "^.+\\.(svg)$": "./node_modules/@plone/volto/jest-svgsystem-transform.js" - }, - "transformIgnorePatterns": [ - "/node_modules/(?!@plone/volto).+\\.js$" - ], - "moduleNameMapper": { - "@plone/volto/cypress/(.*)$": "/node_modules/@plone/volto/cypress/$1", - "@plone/volto/addon-registry": "/node_modules/@plone/volto/addon-registry", - "@plone/volto/webpack-plugins/webpack-less-plugin": "/node_modules/@plone/volto/webpack-plugins/webpack-less-plugin", - "@plone/volto/babel": "/node_modules/@plone/volto/babel", - "@plone/volto/(.*)$": "/node_modules/@plone/volto/src/$1", - "@plone/volto-slate/(.*)$": "/node_modules/@plone/volto/packages/volto-slate/src/$1", - "load-volto-addons": "/node_modules/@plone/volto/jest-addons-loader.js", - "@package/(.*)$": "/src/$1", - "@root/(.*)$": "/src/$1", - "~/(.*)$": "/src/$1", - "\\.(css|less|scss|sass)$": "identity-obj-proxy" - }, - "coverageThreshold": { - "global": { - "branches": 10, - "functions": 10, - "lines": 10, - "statements": 10 - } - }, - "setupFiles": [ - "@plone/volto/test-setup-globals.js", - "@plone/volto/test-setup-config.jsx" - ], - "globals": { - "__DEV__": true - } - }, - "prettier": { - "trailingComma": "all", - "singleQuote": true, - "overrides": [ - { - "files": "*.overrides", - "options": { - "parser": "less" - } - } - ] - }, - "stylelint": { - "extends": [ - "stylelint-config-idiomatic-order" - ], - "plugins": [ - "stylelint-prettier" - ], - "overrides": [ - { - "files": [ - "**/*.less" - ], - "customSyntax": "postcss-less" - }, - { - "files": [ - "**/*.overrides" - ], - "customSyntax": "postcss-less" - }, - { - "files": [ - "**/*.scss" - ], - "customSyntax": "postcss-scss" - } - ], - "rules": { - "prettier/prettier": true, - "rule-empty-line-before": [ - "always-multi-line", - { - "except": [ - "first-nested" - ], - "ignore": [ - "after-comment" - ] - } - ] - }, - "ignoreFiles": "theme/themes/default/**/*.overrides" - }, - "browserslist": [ - ">1%", - "last 4 versions", - "Firefox ESR", - "not ie 11", - "not dead" - ], - "engines": { - "node": "^16 || ^18 || ^20" - }, - "dependencies": { - "@loadable/component": "5.14.1", - "@loadable/server": "5.14.0", - "@plone/registry": "workspace:*", - "@plone/scripts": "workspace:*", - "@plone/volto": "workspace:*", - "@plone/volto-slate": "workspace:*", - "@redux-devtools/extension": "^3.3.0", - "classnames": "2.2.6", - "connected-react-router": "6.8.0", - "debug": "4.3.2", - "decorate-component-with-props": "1.2.1", - "dependency-graph": "0.10.0", - "detect-browser": "5.1.0", - "diff": "3.5.0", - "express": "4.19.2", - "filesize": "6", - "github-slugger": "1.4.0", - "history": "4.10.1", - "hoist-non-react-statics": "3.3.2", - "http-proxy-middleware": "2.0.1", - "image-extensions": "1.1.0", - "immutable": "3", - "is-hotkey": "0.2.0", - "is-url": "1.2.4", - "jotai": "2.0.3", - "jwt-decode": "2.2.0", - "linkify-it": "3.0.2", - "locale": "0.1.0", - "lodash": "4.17.21", - "lodash-move": "1.1.1", - "moment": "2.29.4", - "object-assign": "4.1.1", - "prepend-http": "2", - "pretty-bytes": "5.3.0", - "prismjs": "1.27.0", - "process": "^0.11.10", - "promise-file-reader": "1.0.2", - "prop-types": "15.7.2", - "query-string": "7.1.0", - "rc-time-picker": "3.7.3", - "react": "18.2.0", - "react-anchor-link-smooth-scroll": "1.0.12", - "react-animate-height": "2.0.17", - "react-beautiful-dnd": "13.0.0", - "react-cookie": "4.1.1", - "react-dates": "21.5.1", - "react-detect-click-outside": "1.1.1", - "react-dnd": "5.0.0", - "react-dnd-html5-backend": "5.0.1", - "react-dom": "18.2.0", - "react-dropzone": "11.1.0", - "react-fast-compare": "2.0.4", - "react-image-gallery": "1.2.7", - "react-intersection-observer": "9.1.0", - "react-intl": "3.12.1", - "react-intl-redux": "2.3.0", - "react-medium-image-zoom": "3.0.15", - "react-popper": "^2.3.0", - "react-redux": "8.1.2", - "react-router": "5.2.0", - "react-router-config": "5.1.1", - "react-router-dom": "5.2.0", - "react-router-hash-link": "2.4.3", - "react-select": "4.3.1", - "react-select-async-paginate": "0.5.3", - "react-share": "2.3.1", - "react-side-effect": "2.1.2", - "react-simple-code-editor": "0.7.1", - "react-sortable-hoc": "2.0.0", - "react-test-renderer": "18.2.0", - "react-toastify": "5.5.0", - "react-transition-group": "4.4.5", - "react-virtualized": "9.22.3", - "redux": "4.2.1", - "redux-actions": "3.0.0", - "redux-connect": "10.0.0", - "redux-localstorage-simple": "2.5.1", - "redux-mock-store": "1.5.4", - "redux-thunk": "2.4.2", - "rrule": "2.7.1", - "semantic-ui-less": "2.4.1", - "semantic-ui-react": "2.1.5", - "serialize-javascript": "3.1.0", - "slate": "0.100.0", - "slate-hyperscript": "0.100.0", - "slate-react": "0.98.4", - "superagent": "3.8.2", - "tlds": "1.203.1", - "undoo": "0.5.0", - "universal-cookie": "4.0.4", - "universal-cookie-express": "4.0.3", - "url": "^0.11.3", - "use-deep-compare-effect": "1.8.1", - "uuid": "^8.3.2", - "volto-volto-project": "workspace:*" - }, - "devDependencies": { - "@babel/core": "^7.0.0", - "@babel/eslint-parser": "7.22.15", - "@babel/plugin-proposal-export-default-from": "7.18.10", - "@babel/plugin-proposal-export-namespace-from": "7.18.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", - "@jest/globals": "^29.7.0", - "@loadable/babel-plugin": "5.13.2", - "@loadable/webpack-plugin": "5.15.2", - "@plone/scripts": "workspace:*", - "@plone/types": "1.0.0-alpha.11", - "@plone/volto-coresandbox": "workspace:*", - "@sinonjs/fake-timers": "^6.0.1", - "@storybook/addon-actions": "^8.0.4", - "@storybook/addon-controls": "^8.0.4", - "@storybook/addon-docs": "^8.0.4", - "@storybook/addon-essentials": "^8.0.4", - "@storybook/addon-links": "^8.0.4", - "@storybook/addon-webpack5-compiler-babel": "^3.0.3", - "@storybook/builder-webpack5": "^6.5.15", - "@storybook/manager-webpack5": "^6.5.15", - "@storybook/react": "^8.0.4", - "@storybook/react-webpack5": "^8.0.4", - "@storybook/theming": "^8.0.4", - "@testing-library/cypress": "10.0.1", - "@testing-library/jest-dom": "6.4.2", - "@testing-library/react": "14.2.0", - "@testing-library/react-hooks": "8.0.1", - "@types/jest": "^29.5.8", - "@types/loadable__component": "^5.13.9", - "@types/lodash": "^4.14.201", - "@types/react": "^18", - "@types/react-dom": "^18", - "@types/react-router-dom": "^5.3.3", - "@types/react-test-renderer": "18.0.7", - "@types/uuid": "^9.0.2", - "@typescript-eslint/eslint-plugin": "^7.7.0", - "@typescript-eslint/parser": "^7.7.0", - "autoprefixer": "10.4.8", - "axe-core": "4.4.2", - "babel-loader": "9.1.0", - "babel-plugin-add-module-exports": "0.2.1", - "babel-plugin-lodash": "3.3.4", - "babel-plugin-react-intl": "5.1.17", - "babel-plugin-root-import": "6.1.0", - "babel-preset-razzle": "4.2.18", - "bundlewatch": "0.3.3", - "circular-dependency-plugin": "5.2.2", - "css-loader": "5.2.7", - "cypress": "13.13.2", - "cypress-axe": "1.5.0", - "cypress-file-upload": "5.0.8", - "deep-freeze": "0.0.1", - "eslint": "^8.57.0", - "eslint-config-prettier": "^9.1.0", - "eslint-config-react-app": "^7.0.1", - "eslint-import-resolver-alias": "^1.1.2", - "eslint-import-resolver-babel-plugin-root-import": "^1.1.1", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", - "full-icu": "1.4.0", - "glob": "7.1.6", - "html-webpack-plugin": "5.5.0", - "identity-obj-proxy": "3.0.0", - "jest": "26.6.3", - "jest-environment-jsdom": "^26", - "jest-file": "1.0.0", - "jest-junit": "8.0.0", - "jsdom": "^16.7.0", - "jsonwebtoken": "9.0.0", - "less": "3.11.1", - "less-loader": "11.1.0", - "lodash-webpack-plugin": "0.11.6", - "mini-css-extract-plugin": "2.7.2", - "moment-locales-webpack-plugin": "1.2.0", - "mrs-developer": "^2.1.1", - "postcss": "8.4.31", - "postcss-flexbugs-fixes": "5.0.2", - "postcss-less": "6.0.0", - "postcss-load-config": "3.1.4", - "postcss-loader": "7.0.2", - "postcss-overrides": "3.1.4", - "postcss-scss": "4.0.6", - "prettier": "3.2.5", - "razzle": "4.2.18", - "razzle-dev-utils": "4.2.18", - "razzle-plugin-scss": "4.2.18", - "react-docgen-typescript-plugin": "^1.0.5", - "react-error-overlay": "6.0.9", - "react-is": "^18.2.0", - "release-it": "^17.1.1", - "semver": "^7.5.4", - "start-server-and-test": "1.14.0", - "storybook": "^8.0.4", - "style-loader": "3.3.1", - "stylelint": "^16.3.1", - "stylelint-config-idiomatic-order": "10.0.0", - "stylelint-prettier": "5.0.0", - "svg-loader": "0.0.2", - "svgo-loader": "3.0.3", - "terser-webpack-plugin": "5.3.6", - "tmp": "0.2.1", - "ts-jest": "^26.4.2", - "ts-loader": "9.4.4", - "tsconfig": "*", - "typescript": "^5.4.2", - "use-trace-update": "1.3.2", - "wait-on": "6.0.0", - "webpack": "5.90.1", - "webpack-bundle-analyzer": "4.10.1", - "webpack-dev-server": "4.11.1", - "webpack-node-externals": "3.0.0", - "why": "0.6.2" - }, - "theme": "volto-volto-project" -} diff --git a/apps/plone/patches/patchit.sh b/apps/plone/patches/patchit.sh deleted file mode 100644 index 7cd11b5edc..0000000000 --- a/apps/plone/patches/patchit.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -patch --quiet -p0 -N node_modules/razzle/config/createJestConfig.js < patches/razzle-jest.patch diff --git a/apps/plone/patches/razzle-jest.patch b/apps/plone/patches/razzle-jest.patch deleted file mode 100644 index 5b822cf57f..0000000000 --- a/apps/plone/patches/razzle-jest.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- node_modules/razzle/config/createJestConfig.js 2020-05-09 19:48:07.276871617 +0200 -+++ patched.js 2020-05-09 19:50:07.049471664 +0200 -@@ -60,6 +60,7 @@ - 'moduleDirectories', - 'moduleFileExtensions', - 'moduleNameMapper', -+ 'modulePathIgnorePatterns', - 'modulePaths', - 'snapshotSerializers', - 'setupFiles', diff --git a/apps/plone/public/android-chrome-192x192.png b/apps/plone/public/android-chrome-192x192.png deleted file mode 100644 index 5a31bad458..0000000000 Binary files a/apps/plone/public/android-chrome-192x192.png and /dev/null differ diff --git a/apps/plone/public/android-chrome-512x512.png b/apps/plone/public/android-chrome-512x512.png deleted file mode 100644 index b522d84589..0000000000 Binary files a/apps/plone/public/android-chrome-512x512.png and /dev/null differ diff --git a/apps/plone/public/apple-touch-icon.png b/apps/plone/public/apple-touch-icon.png deleted file mode 100644 index f0a5f8bc85..0000000000 Binary files a/apps/plone/public/apple-touch-icon.png and /dev/null differ diff --git a/apps/plone/public/favicon-16x16.png b/apps/plone/public/favicon-16x16.png deleted file mode 100644 index bf6cf158a0..0000000000 Binary files a/apps/plone/public/favicon-16x16.png and /dev/null differ diff --git a/apps/plone/public/favicon-32x32.png b/apps/plone/public/favicon-32x32.png deleted file mode 100644 index 0411131449..0000000000 Binary files a/apps/plone/public/favicon-32x32.png and /dev/null differ diff --git a/apps/plone/public/favicon.ico b/apps/plone/public/favicon.ico deleted file mode 100644 index b9c0d0c0a6..0000000000 Binary files a/apps/plone/public/favicon.ico and /dev/null differ diff --git a/apps/plone/public/icon.svg b/apps/plone/public/icon.svg deleted file mode 100644 index 72640f02bd..0000000000 --- a/apps/plone/public/icon.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/plone/public/index.html.spa b/apps/plone/public/index.html.spa deleted file mode 100644 index 27e655dcab..0000000000 --- a/apps/plone/public/index.html.spa +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - React App - - - - - -
- URL Management -
-
-
- Alternative url path (Required) -
-

- 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. -

-
-
- -
-
-
- Target Path (Required) -
-

- Enter the absolute path of the target. Target must exist or be an existing alternative url path to the target. -

-
-
- -
-
- -
-
-
+ + +
-
- All existing alternative urls for this site + Existing alternative URLs for this site
- Filter by prefix -
-
- +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
-
-
- Manually or automatically added? -
-
- - +
+
+
+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
-
-
- - -
-
-
+ class="field" + />
+
-
-
- -
- Alternative url path → target url path (date and time of creation, manually created yes/no) + Filter +
- + +
+
+ Alternative URL path → target URL path (date and time of creation, manually created yes/no) +
+
+ - - + Select + + + + + + + + + - - - - - - + - - - - - - + + + - - - - + + + + - - + - + - + + + + - + - + - - -
+ Alias + + Date + + Manual +
- - Select - - Alias - - Target - - Date - - Manual -
- -
- -
-
-

- /eventsgreatalias -

-
-

- /events -

-
-

- 2022-05-17T09:38:19+00:00 -

-
+ + true +
+
-

- true -

-
+
-

- /events -

-
+ + /eventsincredible +
+   → + /events + +
+ + +
+ 5/17/2022 + + + + true + + + + +
-
- + +
diff --git a/packages/volto/src/components/manage/Diff/DiffField.jsx b/packages/volto/src/components/manage/Diff/DiffField.jsx index 23582788d3..a9bad2afb2 100644 --- a/packages/volto/src/components/manage/Diff/DiffField.jsx +++ b/packages/volto/src/components/manage/Diff/DiffField.jsx @@ -4,7 +4,6 @@ */ import React from 'react'; -// import { diffWords as dWords } from 'diff'; import { join, map } from 'lodash'; import PropTypes from 'prop-types'; import { Grid } from 'semantic-ui-react'; @@ -13,20 +12,128 @@ import { Provider } from 'react-intl-redux'; import { createBrowserHistory } from 'history'; import { ConnectedRouter } from 'connected-react-router'; import { useSelector } from 'react-redux'; - +import config from '@plone/volto/registry'; import { Api } from '@plone/volto/helpers'; import configureStore from '@plone/volto/store'; -import { DefaultView } from '@plone/volto/components/'; +import { RenderBlocks } from '@plone/volto/components'; import { serializeNodes } from '@plone/volto-slate/editor/render'; - import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; -/** - * Enhanced diff words utility - * @function diffWords - * @param oneStr Field one - * @param twoStr Field two - */ +const isHtmlTag = (str) => { + // Match complete HTML tags, including: + // 1. Opening tags like
, , ... + // 2. Self-closing tags like ,
+ // 3. Closing tags like
+ return /^<([a-zA-Z]+[0-9]*)\b[^>]*>|^<\/([a-zA-Z]+[0-9]*)\b[^>]*>$|^<([a-zA-Z]+[0-9]*)\b[^>]*\/>$/.test( + str, + ); +}; + +const splitWords = (str) => { + if (typeof str !== 'string') return str; + if (!str) return []; + + const result = []; + let currentWord = ''; + let insideTag = false; + let insideSpecialTag = false; + let tagBuffer = ''; + + // Special tags that should not be split (e.g., , ... ) + const specialTags = ['img', 'svg']; + + for (let i = 0; i < str.length; i++) { + const char = str[i]; + + // Start of an HTML tag + if (char === '<') { + if (currentWord) { + result.push(currentWord); // Push text before the tag + currentWord = ''; + } + insideTag = true; + tagBuffer += char; + } + // End of an HTML tag + else if (char === '>') { + tagBuffer += char; + insideTag = false; + + // Check if the tagBuffer contains a special tag + const tagNameMatch = tagBuffer.match(/^<\/?([a-zA-Z]+[0-9]*)\b/); + if (tagNameMatch && specialTags.includes(tagNameMatch[1])) { + insideSpecialTag = + tagNameMatch[0].startsWith('<') && !tagNameMatch[0].startsWith(' { + if (!isHtmlTag(value)) { + if (part.removed && (side === 'left' || side === 'unified')) { + return `${value}`; + } else if (part.removed) return ''; + else if (part.added && (side === 'right' || side === 'unified')) { + return `${value}`; + } else if (part.added) return ''; + return value; + } else { + if (side === 'unified' && part.added) return value; + else if (side === 'unified' && part.removed) return ''; + if (part.removed && side === 'left') { + return value; + } else if (part.removed) return ''; + else if (part.added && side === 'right') { + return value; + } else if (part.added) return ''; + return value; + } +}; /** * Diff field component. @@ -36,6 +143,7 @@ import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; * @param {Object} schema Field schema * @returns {string} Markup of the component. */ + const DiffField = ({ one, two, @@ -51,7 +159,10 @@ const DiffField = ({ timeStyle: 'short', }; const diffWords = (oneStr, twoStr) => { - return diffLib.diffWords(String(oneStr), String(twoStr)); + return diffLib.diffArrays( + splitWords(String(oneStr)), + splitWords(String(twoStr)), + ); }; let parts, oneArray, twoArray; @@ -78,14 +189,14 @@ const DiffField = ({ ReactDOMServer.renderToStaticMarkup( - + , ), ReactDOMServer.renderToStaticMarkup( - + , ), @@ -116,7 +227,30 @@ const DiffField = ({ } case 'textarea': default: - parts = diffWords(one, two); + const Widget = config.widgets?.views?.widget?.[schema.widget]; + + if (Widget) { + const api = new Api(); + const history = createBrowserHistory(); + const store = configureStore(window.__data, history, api); + parts = diffWords( + ReactDOMServer.renderToStaticMarkup( + + + + + , + ), + ReactDOMServer.renderToStaticMarkup( + + + + + , + ), + ); + } else parts = diffWords(one, two); + break; } } else if (schema.type === 'object') { @@ -128,6 +262,7 @@ const DiffField = ({ } else { parts = diffWords(one?.title || one, two?.title || two); } + return ( @@ -140,14 +275,12 @@ const DiffField = ({ - (part.removed && - `${part.value}`) || - (!part.added && `${part.value}`) || - '', - ), + map(parts, (part) => { + let combined = (part.value || []).reduce((acc, value) => { + return acc + formatDiffPart(part, value, 'left'); + }, ''); + return combined; + }), '', ), }} @@ -157,14 +290,12 @@ const DiffField = ({ - (part.added && - `${part.value}`) || - (!part.removed && `${part.value}`) || - '', - ), + map(parts, (part) => { + let combined = (part.value || []).reduce((acc, value) => { + return acc + formatDiffPart(part, value, 'right'); + }, ''); + return combined; + }), '', ), }} @@ -178,15 +309,12 @@ const DiffField = ({ - (part.removed && - `${part.value}`) || - (part.added && - `${part.value}`) || - (!part.added && `${part.value}`), - ), + map(parts, (part) => { + let combined = (part.value || []).reduce((acc, value) => { + return acc + formatDiffPart(part, value, 'unified'); + }, ''); + return combined; + }), '', ), }} diff --git a/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap b/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap index 8512803d3c..8f3ea9474a 100644 --- a/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap +++ b/packages/volto/src/components/manage/Diff/__snapshots__/Diff.test.jsx.snap @@ -232,16 +232,17 @@ exports[`Diff renders a diff component 1`] = ` class="top aligned six wide column" > - - My - + My old - - title + + + title @@ -249,21 +250,17 @@ exports[`Diff renders a diff component 1`] = ` class="top aligned six wide column" > - - My - + My new - - title - + - , + title, diff --git a/packages/volto/src/components/manage/Diff/__snapshots__/DiffField.test.jsx.snap b/packages/volto/src/components/manage/Diff/__snapshots__/DiffField.test.jsx.snap index 2bbf810620..0871f46671 100644 --- a/packages/volto/src/components/manage/Diff/__snapshots__/DiffField.test.jsx.snap +++ b/packages/volto/src/components/manage/Diff/__snapshots__/DiffField.test.jsx.snap @@ -103,16 +103,14 @@ exports[`DiffField renders a datetime field 1`] = ` - Tuesday + Tuesday, - Monday - - - , April 25, + Monday, + April 25, @@ -123,9 +121,7 @@ exports[`DiffField renders a datetime field 1`] = ` > 2016 - - at 2:14 PM - + at 2:14 PM @@ -155,34 +151,26 @@ exports[`DiffField renders a diff field in split mode 1`] = ` class="top aligned six wide column" > - - My - + My old - - string - + string
- - My - + My new - - string - + string
@@ -212,9 +200,7 @@ exports[`DiffField renders a diff field in unified mode 1`] = ` class="top aligned sixteen wide column" > - - My - + My @@ -225,9 +211,7 @@ exports[`DiffField renders a diff field in unified mode 1`] = ` > new - - string - + string @@ -257,9 +241,7 @@ exports[`DiffField renders a richtext field 1`] = ` class="top aligned sixteen wide column" > - - My - + My @@ -270,9 +252,7 @@ exports[`DiffField renders a richtext field 1`] = ` > new - - string - + string @@ -302,9 +282,7 @@ exports[`DiffField renders a textarea field 1`] = ` class="top aligned sixteen wide column" > - - My - + My @@ -315,9 +293,7 @@ exports[`DiffField renders a textarea field 1`] = ` > new - - string - + string @@ -347,9 +323,7 @@ exports[`DiffField renders an array field 1`] = ` class="top aligned sixteen wide column" > - - one, - + one, @@ -389,9 +363,7 @@ exports[`DiffField renders an array of objects field 1`] = ` class="top aligned sixteen wide column" > - - one, - + one, diff --git a/packages/volto/src/components/manage/Edit/Edit.jsx b/packages/volto/src/components/manage/Edit/Edit.jsx index 333f6cf651..93efab9a7c 100644 --- a/packages/volto/src/components/manage/Edit/Edit.jsx +++ b/packages/volto/src/components/manage/Edit/Edit.jsx @@ -346,11 +346,15 @@ class Edit extends Component { <> {this.props.content?.language && ( diff --git a/packages/volto/src/components/manage/Form/Form.jsx b/packages/volto/src/components/manage/Form/Form.jsx index 8b108d7dde..41a4c3f97c 100644 --- a/packages/volto/src/components/manage/Form/Form.jsx +++ b/packages/volto/src/components/manage/Form/Form.jsx @@ -274,19 +274,22 @@ class Form extends Component { selected: null, }); } - - if (requestError && prevProps.requestError !== requestError) { + if (requestError) { errors = FormValidation.giveServerErrorsToCorrespondingFields(requestError); - activeIndex = FormValidation.showFirstTabWithErrors({ - errors, - schema: this.props.schema, - }); - - this.setState({ - errors, - activeIndex, - }); + if ( + !isEqual(prevProps.requestError, requestError) || + !isEqual(this.state.errors, errors) + ) { + activeIndex = FormValidation.showFirstTabWithErrors({ + errors, + schema: this.props.schema, + }); + this.setState({ + errors, + activeIndex, + }); + } } if (this.props.onChangeFormData) { @@ -563,13 +566,11 @@ class Form extends Component { } }); } - if (keys(errors).length > 0 || keys(blocksErrors).length > 0) { const activeIndex = FormValidation.showFirstTabWithErrors({ errors, schema: this.props.schema, }); - this.setState({ errors: { ...errors, @@ -580,14 +581,23 @@ class Form extends Component { if (keys(errors).length > 0) { // Changes the focus to the metadata tab in the sidebar if error - Object.keys(errors).forEach((err) => - toast.error( - , - ), + toast.error( + + {Object.keys(errors).map((err, index) => ( +
  • + + {this.props.schema.properties[err].title || err}: + {' '} + {errors[err]} +
  • + ))} + + } + />, ); this.props.setSidebarTab(0); } else if (keys(blocksErrors).length > 0) { @@ -714,7 +724,6 @@ class Form extends Component { const schema = this.removeBlocksLayoutFields(originalSchema); const Container = config.getComponent({ name: 'Container' }).component || SemanticContainer; - return this.props.visual ? ( // Removing this from SSR is important, since react-beautiful-dnd supports SSR, // but draftJS don't like it much and the hydration gets messed up diff --git a/packages/volto/src/components/manage/Form/ModalForm.jsx b/packages/volto/src/components/manage/Form/ModalForm.jsx index ba2f5b0a30..4bc39652c1 100644 --- a/packages/volto/src/components/manage/Form/ModalForm.jsx +++ b/packages/volto/src/components/manage/Form/ModalForm.jsx @@ -70,6 +70,7 @@ class ModalForm extends Component { required: PropTypes.arrayOf(PropTypes.string), }).isRequired, title: PropTypes.string.isRequired, + description: PropTypes.objectOf(PropTypes.any), formData: PropTypes.objectOf(PropTypes.any), submitError: PropTypes.string, onSubmit: PropTypes.func.isRequired, @@ -211,7 +212,7 @@ class ModalForm extends Component { * @returns {string} Markup for the component. */ render() { - const { schema, onCancel } = this.props; + const { schema, onCancel, description } = this.props; const currentFieldset = schema.fieldsets[this.state.currentTab]; const fields = map(currentFieldset.fields, (field) => ({ @@ -245,6 +246,7 @@ class ModalForm extends Component { onSubmit={this.onSubmit} error={state_errors || Boolean(this.props.submitError)} > + {description} {state_errors ? ( ', () => {
      - {pluggables.map((p) => ( - <>{p()} + {pluggables.map((p, index) => ( + {p()} ))}
    @@ -92,7 +92,7 @@ describe('', () => {
      {pluggables.map((p) => ( - <>{p()} + {p()} ))}
    diff --git a/packages/volto/src/components/manage/Sidebar/ObjectBrowserNav.jsx b/packages/volto/src/components/manage/Sidebar/ObjectBrowserNav.jsx index 3c837d0e5a..bcecef9bda 100644 --- a/packages/volto/src/components/manage/Sidebar/ObjectBrowserNav.jsx +++ b/packages/volto/src/components/manage/Sidebar/ObjectBrowserNav.jsx @@ -51,6 +51,7 @@ const ObjectBrowserNav = ({ currentSearchResults.items.map((item) => view === 'icons' ? (
  • diff --git a/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx b/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx index f963dfb1ff..5c08377b7e 100644 --- a/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx +++ b/packages/volto/src/components/manage/Sidebar/SidebarPopup.jsx @@ -16,6 +16,13 @@ const SidebarPopup = (props) => { onClose(); }; + const handleEscapeKey = (e) => { + if (open && e.key === 'Escape') { + onClose(); + e.stopPropagation(); + } + }; + const [isClient, setIsClient] = React.useState(false); React.useEffect(() => { setIsClient(true); @@ -23,11 +30,12 @@ const SidebarPopup = (props) => { React.useEffect(() => { document.addEventListener('mousedown', handleClickOutside, false); + document.addEventListener('keyup', handleEscapeKey, false); return () => { document.removeEventListener('mousedown', handleClickOutside, false); + document.removeEventListener('keyup', handleEscapeKey, false); }; }); - return ( <> {overlay && ( diff --git a/packages/volto/src/components/manage/Toast/Toast.jsx b/packages/volto/src/components/manage/Toast/Toast.jsx index 96f4355656..558eb8bd44 100644 --- a/packages/volto/src/components/manage/Toast/Toast.jsx +++ b/packages/volto/src/components/manage/Toast/Toast.jsx @@ -29,7 +29,7 @@ const Toast = (props) => {
    {title &&

    {title}

    } -

    {content}

    +
    {content}
    ); @@ -37,7 +37,7 @@ const Toast = (props) => { Toast.propTypes = { title: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), - content: PropTypes.string.isRequired, + content: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, info: PropTypes.bool, success: PropTypes.bool, error: PropTypes.bool, diff --git a/packages/volto/src/components/manage/Toast/__snapshots__/Toast.test.jsx.snap b/packages/volto/src/components/manage/Toast/__snapshots__/Toast.test.jsx.snap index 1d65445606..3d01fb4fca 100644 --- a/packages/volto/src/components/manage/Toast/__snapshots__/Toast.test.jsx.snap +++ b/packages/volto/src/components/manage/Toast/__snapshots__/Toast.test.jsx.snap @@ -26,9 +26,9 @@ Array [

    I'm a title

    -

    +

    This is the content, lorem ipsum -

    +
    , ] `; diff --git a/packages/volto/src/components/manage/Toolbar/More.jsx b/packages/volto/src/components/manage/Toolbar/More.jsx index a40a74fe9f..cef48d19cc 100644 --- a/packages/volto/src/components/manage/Toolbar/More.jsx +++ b/packages/volto/src/components/manage/Toolbar/More.jsx @@ -311,8 +311,8 @@ const More = (props) => {
      - {pluggables.map((p) => ( - <>{p()} + {pluggables.map((p, index) => ( + {p()} ))}
    diff --git a/packages/volto/src/components/manage/Toolbar/PersonalTools.jsx b/packages/volto/src/components/manage/Toolbar/PersonalTools.jsx index 0a11e8fd6a..7d98a568d1 100644 --- a/packages/volto/src/components/manage/Toolbar/PersonalTools.jsx +++ b/packages/volto/src/components/manage/Toolbar/PersonalTools.jsx @@ -9,11 +9,7 @@ import { FormattedMessage, useIntl, defineMessages } from 'react-intl'; import { Icon } from '@plone/volto/components'; import { getUser } from '@plone/volto/actions'; import { Pluggable } from '@plone/volto/components/manage/Pluggable'; -import { - expandToBackendURL, - getBaseUrl, - userHasRoles, -} from '@plone/volto/helpers'; +import { expandToBackendURL, getBaseUrl } from '@plone/volto/helpers'; import logoutSVG from '@plone/volto/icons/log-out.svg'; import rightArrowSVG from '@plone/volto/icons/right-key.svg'; import backSVG from '@plone/volto/icons/back.svg'; @@ -50,7 +46,11 @@ const PersonalTools = (props) => { const token = useSelector((state) => state.userSession.token, shallowEqual); const user = useSelector((state) => state.users.user); const userId = token ? jwtDecode(token).sub : ''; - + const siteSetupAction = useSelector((state) => + state.actions?.actions?.user?.find( + (action) => action?.id === 'plone_setup', + ), + ); useEffect(() => { dispatch(getUser(userId)); }, [dispatch, userId]); @@ -127,7 +127,7 @@ const PersonalTools = (props) => {
  • - {userHasRoles(user, ['Site Administrator', 'Manager']) && ( + {siteSetupAction && (
  • diff --git a/packages/volto/src/components/manage/Toolbar/PersonalTools.test.jsx b/packages/volto/src/components/manage/Toolbar/PersonalTools.test.jsx index 5a52a74cc2..4dd3122e7c 100644 --- a/packages/volto/src/components/manage/Toolbar/PersonalTools.test.jsx +++ b/packages/volto/src/components/manage/Toolbar/PersonalTools.test.jsx @@ -29,6 +29,16 @@ describe('Toolbar Personal Tools component', () => { is_folderish: true, }, }, + actions: { + actions: { + user: [ + { + id: 'plone_setup', + title: 'Site Setup', + }, + ], + }, + }, intl: { locale: 'en', messages: {}, @@ -70,6 +80,16 @@ describe('Toolbar Personal Tools component', () => { is_folderish: true, }, }, + actions: { + actions: { + user: [ + { + id: 'plone_setup', + title: 'Site Setup', + }, + ], + }, + }, intl: { locale: 'en', messages: {}, @@ -112,6 +132,57 @@ describe('Toolbar Personal Tools component', () => { is_folderish: true, }, }, + actions: { + actions: { + user: [ + { + id: 'plone_setup', + title: 'Site Setup', + }, + ], + }, + }, + intl: { + locale: 'en', + messages: {}, + }, + }); + const component = renderer.create( + + + + {}} + theToolbar={{ + current: { getBoundingClientRect: () => ({ width: '320' }) }, + }} + /> + + + , + ); + const json = component.toJSON(); + expect(json).toMatchSnapshot(); + }); + + it('renders an Toolbar Personal Tools component without Site Setup access', () => { + const store = mockStore({ + users: { + user: { + fullname: 'regular_user', + email: 'user@plone.org', + roles: ['Member'], + }, + }, + userSession: { + token: jwt.sign({ sub: 'regular_user' }, 'secret'), + }, + content: { + data: { + '@type': 'Folder', + is_folderish: true, + }, + }, intl: { locale: 'en', messages: {}, diff --git a/packages/volto/src/components/manage/Toolbar/__snapshots__/PersonalTools.test.jsx.snap b/packages/volto/src/components/manage/Toolbar/__snapshots__/PersonalTools.test.jsx.snap index 5e7695369b..e7229cadcb 100644 --- a/packages/volto/src/components/manage/Toolbar/__snapshots__/PersonalTools.test.jsx.snap +++ b/packages/volto/src/components/manage/Toolbar/__snapshots__/PersonalTools.test.jsx.snap @@ -513,3 +513,153 @@ exports[`Toolbar Personal Tools component renders an Toolbar Personal Tools comp `; + +exports[`Toolbar Personal Tools component renders an Toolbar Personal Tools component without Site Setup access 1`] = ` +
    +
    + +
    +
    + +
    +
    + +
    +
    +`; diff --git a/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx b/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx index 2670cc84bc..dc361195b3 100644 --- a/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/ArrayWidget.jsx @@ -314,6 +314,7 @@ class ArrayWidget extends Component { // small fix for https://github.com/clauderic/react-sortable-hoc/pull/352: getHelperDimensions={({ node }) => node.getBoundingClientRect()} id={`field-${this.props.id}`} + aria-labelledby={`fieldset-${this.props.fieldSet}-field-label-${this.props.id}`} key={this.props.id} isDisabled={this.props.disabled || this.props.isDisabled} className="react-select-container" diff --git a/packages/volto/src/components/manage/Widgets/FormFieldWrapper.jsx b/packages/volto/src/components/manage/Widgets/FormFieldWrapper.jsx index ee60438be4..732b4ba281 100644 --- a/packages/volto/src/components/manage/Widgets/FormFieldWrapper.jsx +++ b/packages/volto/src/components/manage/Widgets/FormFieldWrapper.jsx @@ -8,6 +8,8 @@ import { Form, Grid, Icon as IconOld, Label } from 'semantic-ui-react'; import { map } from 'lodash'; import cx from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; +import LanguageSVG from '@plone/volto/icons/language.svg'; +import { Icon } from '@plone/volto/components'; const messages = defineMessages({ edit: { @@ -22,6 +24,11 @@ const messages = defineMessages({ id: 'Language independent field.', defaultMessage: 'Language independent field.', }, + language_independent_icon_title: { + id: 'Language independent icon title', + defaultMessage: + 'This is a language independent field. Any value you enter here will overwrite the corresponding field of all members of the translation group when you save this form.', + }, }); /** * FormFieldWrapper component class. @@ -91,6 +98,9 @@ class FormFieldWrapper extends Component { noForInFieldLabel, multilingual_options, } = this.props; + + const languageIndependent = multilingual_options?.language_independent; + const wdg = ( <> {this.props.children} @@ -112,9 +122,7 @@ class FormFieldWrapper extends Component { description ? 'help' : '', className, `field-wrapper-${id}`, - multilingual_options?.language_independent - ? 'language-independent-field' - : null, + languageIndependent ? 'language-independent-field' : null, )} > @@ -133,6 +141,18 @@ class FormFieldWrapper extends Component { /> )} {title} + {languageIndependent && ( +
    + +
    + )} diff --git a/packages/volto/src/components/manage/Widgets/IdWidget.jsx b/packages/volto/src/components/manage/Widgets/IdWidget.jsx index 0841a6dd25..11c4f23a68 100644 --- a/packages/volto/src/components/manage/Widgets/IdWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/IdWidget.jsx @@ -7,7 +7,7 @@ import { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import { useSelector, useDispatch } from 'react-redux'; import { Input } from 'semantic-ui-react'; -import { compact, concat, keys, map, union, uniq } from 'lodash'; +import { compact, concat, map, union, uniq } from 'lodash'; import { defineMessages, useIntl } from 'react-intl'; import { Icon } from '@plone/volto/components'; @@ -46,8 +46,7 @@ const IdWidget = (props) => { const intl = useIntl(); const dispatch = useDispatch(); const ref = useRef(); - - const indexes = useSelector((state) => keys(state.querystring.indexes)); + const indexes = useSelector((state) => state.querystring.indexes); const [errors, setError] = useState([]); const [reservedIds] = useState( @@ -62,11 +61,11 @@ const IdWidget = (props) => { ), ), ); - const fieldValidation = (values) => { + const fieldValidation = (value) => { const error = []; // Check reserved id's - if (reservedIds.indexOf(values) !== -1) { + if (reservedIds.indexOf(value) !== -1) { error.push(intl.formatMessage(messages.reservedId)); } @@ -74,14 +73,14 @@ const IdWidget = (props) => { if ( // eslint-disable-next-line no-control-regex !/^(?!.*\\)(?!\+\+)(?!@@)(?!.*request)(?!.*contributors)(?!aq_)(?!.*__)(?!_)(?!((^|\/)\.\.?($|\/)|^"\s*"$))(?!.*[A-Z])(?:(?![\r\n<>/?&#\x00-\x1F\x7F])['\x00-\x7F\u0080-\uFFFF. _])*$/.test( - values, + value, ) ) { error.push(intl.formatMessage(messages.invalidCharacters)); } // Check indexes - if (indexes.indexOf(values) !== -1) { + if (value in indexes) { error.push(intl.formatMessage(messages.reservedId)); } diff --git a/packages/volto/src/components/manage/Widgets/SelectWidget.jsx b/packages/volto/src/components/manage/Widgets/SelectWidget.jsx index 64b0cdea69..0c9d61e12f 100644 --- a/packages/volto/src/components/manage/Widgets/SelectWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/SelectWidget.jsx @@ -224,6 +224,7 @@ class SelectWidget extends Component { id={`field-${id}`} key={choices} name={id} + aria-labelledby={`fieldset-${this.props.fieldSet}-field-label-${id}`} menuShouldScrollIntoView={false} isDisabled={disabled} isSearchable={true} diff --git a/packages/volto/src/components/manage/Widgets/TokenWidget.jsx b/packages/volto/src/components/manage/Widgets/TokenWidget.jsx index b38de6897d..cad1f0bdcb 100644 --- a/packages/volto/src/components/manage/Widgets/TokenWidget.jsx +++ b/packages/volto/src/components/manage/Widgets/TokenWidget.jsx @@ -173,6 +173,7 @@ class TokenWidget extends Component { +