diff --git a/.github/actions/check-pr-status/jest.config.js b/.github/actions/check-pr-status/jest.config.js new file mode 100644 index 0000000000..35fb10228e --- /dev/null +++ b/.github/actions/check-pr-status/jest.config.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = { + preset: '../../../jest-preset.unit.js', + displayName: 'Github action check-pr-status', +}; diff --git a/.github/actions/check-pr-status/package.json b/.github/actions/check-pr-status/package.json index dbbac76254..38ac8703b1 100644 --- a/.github/actions/check-pr-status/package.json +++ b/.github/actions/check-pr-status/package.json @@ -1,6 +1,6 @@ { "name": "check-pr-status", - "version": "4.10.1", + "version": "4.10.5", "main": "dist/index.js", "license": "MIT", "private": true, diff --git a/.github/actions/yarn-nm-install/action.yml b/.github/actions/yarn-nm-install/action.yml new file mode 100644 index 0000000000..8ae662224b --- /dev/null +++ b/.github/actions/yarn-nm-install/action.yml @@ -0,0 +1,88 @@ +######################################################################################## +# "yarn install" composite action for yarn 3/4+ and "nodeLinker: node-modules" # +#--------------------------------------------------------------------------------------# +# Requirement: @setup/node should be run before # +# # +# Usage in workflows steps: # +# # +# - name: πŸ“₯ Monorepo install # +# uses: ./.github/actions/yarn-nm-install # +# with: # +# enable-corepack: false # (default) # +# cache-install-state: false # (default) # +# cache-node-modules: false # (default) # +# # +# Reference: # +# - latest: https://gist.github.com/belgattitude/042f9caf10d029badbde6cf9d43e400a # +######################################################################################## + +name: 'Monorepo install (yarn)' +description: 'Run yarn install with node_modules linker and cache enabled' +inputs: + enable-corepack: + description: 'Enable corepack' + required: false + default: 'false' + cache-node-modules: + description: 'Cache node_modules, might speed up link step (invalidated lock/os/node-version/branch)' + required: false + default: 'false' + cache-install-state: + description: 'Cache yarn install state, might speed up resolution step when node-modules cache is activated (invalidated lock/os/node-version/branch)' + required: false + default: 'false' + +runs: + using: 'composite' + + steps: + - name: βš™οΈ Enable Corepack + if: ${{ inputs.enable-corepack }} == 'true' + shell: bash + run: corepack enable + + - name: βš™οΈ Expose yarn config as "$GITHUB_OUTPUT" + id: yarn-config + shell: bash + env: + YARN_ENABLE_GLOBAL_CACHE: "false" + run: | + echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + echo "CURRENT_NODE_VERSION="node-$(node --version)"" >> $GITHUB_OUTPUT + echo "CURRENT_BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's,/,-,g')" >> $GITHUB_OUTPUT + + - name: ♻️ Restore yarn cache + uses: actions/cache@v3 + id: yarn-download-cache + with: + path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }} + key: yarn-download-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} + restore-keys: | + yarn-download-cache- + + - name: ♻️ Restore node_modules + if: inputs.cache-node-modules == 'true' + id: yarn-nm-cache + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: yarn-nm-cache-${{ runner.os }}-${{ steps.yarn-config.outputs.CURRENT_NODE_VERSION }}-${{ steps.yarn-config.outputs.CURRENT_BRANCH }}-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} + + - name: ♻️ Restore yarn install state + if: inputs.cache-install-state == 'true' && inputs.cache-node-modules == 'true' + id: yarn-install-state-cache + uses: actions/cache@v3 + with: + path: .yarn/ci-cache + key: yarn-install-state-cache-${{ runner.os }}-${{ steps.yarn-config.outputs.CURRENT_NODE_VERSION }}-${{ steps.yarn-config.outputs.CURRENT_BRANCH }}-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} + + - name: πŸ“₯ Install dependencies + shell: bash + run: yarn install --immutable --inline-builds + env: + # Overrides/align yarnrc.yml options (v3, v4) for a CI context + YARN_ENABLE_GLOBAL_CACHE: "false" # Use local cache folder to keep downloaded archives + YARN_NM_MODE: "hardlinks-local" # Reduce node_modules size + YARN_INSTALL_STATE_PATH: ".yarn/ci-cache/install-state.gz" # Might speed up resolutions when node_modules present + # Other environment variables + HUSKY: '0' # By default do not run HUSKY install diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6bee948be8..c96b9fd8f8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,9 @@ updates: directory: / schedule: interval: weekly + day: sunday + time: '22:00' + open-pull-requests-limit: 10 versioning-strategy: increase ignore: # Only allow patch as minor babel versions need to be upgraded all together @@ -21,9 +24,12 @@ updates: - 'source: dependencies' - 'pr: chore' - package-ecosystem: github-actions + open-pull-requests-limit: 10 directory: / schedule: interval: weekly + day: sunday + time: '22:00' labels: - 'source: dependencies' - 'pr: chore' diff --git a/.github/filters.yaml b/.github/filters.yaml new file mode 100644 index 0000000000..a320cb6c57 --- /dev/null +++ b/.github/filters.yaml @@ -0,0 +1,18 @@ +backend: + - '.github/actions/yarn-nm-install/*.yml' + - '.github/workflows/**' + - 'packages/**/package.json' + - 'packages/**/server/**/*.(js|ts)' + - 'packages/**/strapi-server.js' + - 'packages/{utils,generators,cli,providers}/**' + - 'packages/core/*/{lib,bin,ee}/**' + - 'api-tests/**' +frontend: + - '.github/actions/yarn-nm-install/*.yml' + - '.github/workflows/**' + - 'packages/**/package.json' + - 'packages/**/admin/src/**' + - 'packages/**/admin/ee/admin/**' + - 'packages/**/strapi-admin.js' + - 'packages/core/helper-plugin/**' + - 'packages/admin-test-utils/**' diff --git a/.github/jest.config.js b/.github/jest.config.js deleted file mode 100644 index 39009ed0a8..0000000000 --- a/.github/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - displayName: '.github', -}; diff --git a/.github/workflows/adminBundleSize.yml b/.github/workflows/adminBundleSize.yml index eac27af848..9a56021089 100644 --- a/.github/workflows/adminBundleSize.yml +++ b/.github/workflows/adminBundleSize.yml @@ -27,8 +27,9 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 18 - path: '**/node_modules' - key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} + + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: preactjs/compressed-size-action@v2 with: diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 51453e4c10..d898f050cc 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -23,7 +23,4 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} - uses: ./.github/actions/security/lockfile diff --git a/.github/workflows/clean-up-pr-caches.yml b/.github/workflows/clean-up-pr-caches.yml new file mode 100644 index 0000000000..2d3e251fb6 --- /dev/null +++ b/.github/workflows/clean-up-pr-caches.yml @@ -0,0 +1,36 @@ +# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries +name: Cleanup caches for closed branches + +on: + pull_request: + types: + - closed + workflow_dispatch: + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: 🧹 Cleanup + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/contributor-doc.yml b/.github/workflows/contributor-doc.yml index aefb2469d5..678a78f2cc 100644 --- a/.github/workflows/contributor-doc.yml +++ b/.github/workflows/contributor-doc.yml @@ -33,8 +33,6 @@ jobs: - uses: actions/setup-node@v3 with: node-version: 18 - path: '**/node_modules' - key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} - name: Install dependencies run: yarn install --immutable diff --git a/.github/workflows/remove-dist-tag.yml b/.github/workflows/remove-dist-tag.yml index 4afe14fa67..1c504c5135 100644 --- a/.github/workflows/remove-dist-tag.yml +++ b/.github/workflows/remove-dist-tag.yml @@ -23,6 +23,6 @@ jobs: with: node-version: 16 - run: yarn - - run: ./scripts/remove-dist-tag + - run: ./scripts/remove-dist-tag.sh env: DIST_TAG: ${{ github.event.inputs.dist-tag }} diff --git a/.github/workflows/skipped_tests.yml b/.github/workflows/skipped_tests.yml index ba007631bc..f6e81dd284 100644 --- a/.github/workflows/skipped_tests.yml +++ b/.github/workflows/skipped_tests.yml @@ -11,12 +11,28 @@ concurrency: cancel-in-progress: true jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + nonDoc: ${{ steps.filter.outputs.nonDoc }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + # For pull requests it's not necessary to checkout the code + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: .github/filters.yaml + lint: name: 'lint (node: ${{ matrix.node }})' runs-on: ubuntu-latest strategy: matrix: - node: [14, 16, 18] + node: [18] steps: - run: echo "Skipped" @@ -34,6 +50,16 @@ jobs: name: 'unit_front (node: ${{ matrix.node }})' needs: [lint] runs-on: ubuntu-latest + strategy: + matrix: + node: [18] + steps: + - run: echo "Skipped" + + build: + name: 'build (node: ${{ matrix.node }})' + needs: [changes, lint, unit_front] + runs-on: ubuntu-latest strategy: matrix: node: [14, 16, 18] diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9dd4e267ff..755a515b1b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,8 +18,25 @@ permissions: actions: read jobs: + changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + backend: ${{ steps.filter.outputs.backend }} + frontend: ${{ steps.filter.outputs.frontend }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: .github/filters.yaml + lint: name: 'lint (node: ${{ matrix.node }})' + needs: [changes] runs-on: ubuntu-latest strategy: matrix: @@ -31,12 +48,9 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - uses: nrwl/nx-set-shas@v3 - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - name: Run build:ts run: yarn nx run-many --target=build:ts --nx-ignore-cycles --skip-nx-cache - name: Run lint @@ -44,7 +58,7 @@ jobs: unit_back: name: 'unit_back (node: ${{ matrix.node }})' - needs: [lint] + needs: [changes, lint] runs-on: ubuntu-latest strategy: matrix: @@ -56,12 +70,9 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - uses: nrwl/nx-set-shas@v3 - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - name: Run build:ts run: yarn nx run-many --target=build:ts --nx-ignore-cycles --skip-nx-cache - name: Run tests @@ -69,7 +80,7 @@ jobs: unit_front: name: 'unit_front (node: ${{ matrix.node }})' - needs: [lint] + needs: [changes, lint] runs-on: ubuntu-latest strategy: matrix: @@ -81,20 +92,17 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - uses: nrwl/nx-set-shas@v3 - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - name: Run build:ts for admin-test-utils - run: yarn build:ts --projects=@strapi/admin-test-utils --skip-nx-cache + run: yarn build --projects=@strapi/admin-test-utils,@strapi/helper-plugin --skip-nx-cache - name: Run test run: yarn nx affected --target=test:front --nx-ignore-cycles build: name: 'build (node: ${{ matrix.node }})' - needs: [lint, unit_front] + needs: [changes, lint, unit_front] runs-on: ubuntu-latest strategy: matrix: @@ -104,17 +112,15 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - name: Build run: yarn build --projects=@strapi/admin,@strapi/helper-plugin api_ce_pg: + if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] + needs: [changes, lint, unit_back, unit_front] name: '[CE] API Integration (postgres, node: ${{ matrix.node }})' strategy: matrix: @@ -143,18 +149,16 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/run-api-tests with: dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi' api_ce_mysql: + if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] + needs: [changes, lint, unit_back, unit_front] name: '[CE] API Integration (mysql:latest, client: ${{ matrix.db_client }}, node: ${{ matrix.node }})' strategy: matrix: @@ -182,18 +186,16 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/run-api-tests with: dbOptions: '--dbclient=${{ matrix.db_client }} --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi' api_ce_mysql_5: + if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] + needs: [changes, lint, unit_back, unit_front] name: '[CE] API Integration (mysql:5, client: ${{ matrix.db_client }} , node: ${{ matrix.node }})' strategy: matrix: @@ -220,33 +222,28 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/run-api-tests with: dbOptions: '--dbclient=${{ matrix.db_client }} --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi' api_ce_sqlite: + if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] + needs: [changes, lint, unit_back, unit_front] name: '[CE] API Integration (sqlite, client: ${{ matrix.sqlite_pkg }}, node: ${{ matrix.node }})' strategy: matrix: node: [14, 16, 18] - sqlite_pkg: ['better-sqlite3', 'sqlite3', '@vscode/sqlite3'] + sqlite_pkg: ['better-sqlite3', 'sqlite3'] steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/run-api-tests env: SQLITE_PKG: ${{ matrix.sqlite_pkg }} @@ -256,9 +253,9 @@ jobs: # EE api_ee_pg: runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] + needs: [changes, lint, unit_back, unit_front] name: '[EE] API Integration (postgres, node: ${{ matrix.node }})' - if: github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') + if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') env: STRAPI_LICENSE: ${{ secrets.strapiLicense }} strategy: @@ -288,11 +285,8 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/run-api-tests with: dbOptions: '--dbclient=postgres --dbhost=localhost --dbport=5432 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi' @@ -300,9 +294,9 @@ jobs: api_ee_mysql: runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] + needs: [changes, lint, unit_back, unit_front] name: '[EE] API Integration (mysql:latest, client: ${{ matrix.db_client }}, node: ${{ matrix.node }})' - if: github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') + if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') env: STRAPI_LICENSE: ${{ secrets.strapiLicense }} strategy: @@ -331,11 +325,8 @@ jobs: - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/run-api-tests with: dbOptions: '--dbclient=${{ matrix.db_client }} --dbhost=localhost --dbport=3306 --dbname=strapi_test --dbusername=strapi --dbpassword=strapi' @@ -343,25 +334,22 @@ jobs: api_ee_sqlite: runs-on: ubuntu-latest - needs: [lint, unit_back, unit_front] + needs: [changes, lint, unit_back, unit_front] name: '[EE] API Integration (sqlite, client: ${{ matrix.sqlite_pkg }}, node: ${{ matrix.node }})' - if: github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') + if: needs.changes.outputs.backend == 'true' && github.event.pull_request.head.repo.full_name == github.repository && !(github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') env: STRAPI_LICENSE: ${{ secrets.strapiLicense }} strategy: matrix: node: [14, 16, 18] - sqlite_pkg: ['better-sqlite3', 'sqlite3', '@vscode/sqlite3'] + sqlite_pkg: ['better-sqlite3', 'sqlite3'] steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/yarn.lock') }} - - run: yarn install --immutable + - name: Monorepo install + uses: ./.github/actions/yarn-nm-install - uses: ./.github/actions/run-api-tests env: SQLITE_PKG: ${{ matrix.sqlite_pkg }} diff --git a/.husky/pre-commit b/.husky/pre-commit index 70b64877d2..5a182ef106 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -2,4 +2,3 @@ . "$(dirname -- "$0")/_/husky.sh" yarn lint-staged -yarn nx affected:lint --uncommitted --nx-ignore-cycles diff --git a/LICENSE b/LICENSE index db018546b5..f63cf15b91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,15 @@ Copyright (c) 2015-present Strapi Solutions SAS - + Portions of the Strapi software are licensed as follows: - -* All software that resides under an "ee/" directory (the β€œEE Software”), if that directory exists, is licensed under the license defined in "ee/LICENSE". - -* All software outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as set forth below. - + +* If you are accessing or using any component of the software that resides under an "ee/" directory, then you are deemed to be using our β€œEnterprise Edition” of the software and you understand and agree that the software is not licensed under the "MIT Expat" license as set forth below but instead, all the software you access is licensed under the license defined in "strapi/packages/core/admin/ee/LICENSE" and located at https://github.com/strapi/strapi/blob/a76b557047e9ef1c168dbf1b6cf879bcc3022de6/packages/core/admin/ee/LICENSE, unless (a) you or the company you represent has signed an alternative agreement referencing this code, then such signed agreement applies or (b) you are using the software in connection with a subscription to our cloud offering, then the terms of the agreement relevant to the cloud offering which you have assented to apply and the software licenses included in that agreement shall apply. + +* If (a) you are not accessing or using the software that resides under an β€œee/” directory and therefore you are only accessing or using our β€œCommunity Edition” of the Software and (b) you have no registered account on our cloud offering, then we are providing you the software under the "MIT Expat" license as set forth below. + MIT Expat License - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/api-tests/core/admin/ee/review-workflows.test.api.js b/api-tests/core/admin/ee/review-workflows.test.api.js index 4246c9aec9..d76f174816 100644 --- a/api-tests/core/admin/ee/review-workflows.test.api.js +++ b/api-tests/core/admin/ee/review-workflows.test.api.js @@ -279,10 +279,25 @@ describeOnCondition(edition === 'EE')('Review workflows', () => { ]; }); + test("It should assign a default color to stages if they don't have one", async () => { + await requests.admin.put(`/admin/review-workflows/workflows/${testWorkflow.id}/stages`, { + body: { + data: [defaultStage, { id: secondStage.id, name: secondStage.name, color: '#000000' }], + }, + }); + + const workflowRes = await requests.admin.get( + `/admin/review-workflows/workflows/${testWorkflow.id}?populate=*` + ); + + expect(workflowRes.status).toBe(200); + expect(workflowRes.body.data.stages[0].color).toBe('#4945FF'); + expect(workflowRes.body.data.stages[1].color).toBe('#000000'); + }); test("It shouldn't be available for public", async () => { const stagesRes = await requests.public.put( `/admin/review-workflows/workflows/${testWorkflow.id}/stages`, - stagesUpdateData + { body: { data: stagesUpdateData } } ); const workflowRes = await requests.public.get( `/admin/review-workflows/workflows/${testWorkflow.id}` @@ -353,6 +368,19 @@ describeOnCondition(edition === 'EE')('Review workflows', () => { expect(workflowRes.body.data).toBeUndefined(); } }); + test('It should throw an error if trying to create more than 200 stages', async () => { + const stagesRes = await requests.admin.put( + `/admin/review-workflows/workflows/${testWorkflow.id}/stages`, + { body: { data: Array(201).fill({ name: 'new stage' }) } } + ); + + if (hasRW) { + expect(stagesRes.status).toBe(400); + expect(stagesRes.body.error).toBeDefined(); + expect(stagesRes.body.error.name).toEqual('ValidationError'); + expect(stagesRes.body.error.message).toBeDefined(); + } + }); }); describe('Enabling/Disabling review workflows on a content type', () => { @@ -416,7 +444,7 @@ describeOnCondition(edition === 'EE')('Review workflows', () => { }); }); - describe('update a stage on an entity', () => { + describe('Update a stage on an entity', () => { describe('Review Workflow is enabled', () => { beforeAll(async () => { await updateContentType(productUID, { diff --git a/api-tests/core/content-type-builder/collection-type.test.api.js b/api-tests/core/content-type-builder/collection-type.test.api.js index c6147264cf..d050629336 100644 --- a/api-tests/core/content-type-builder/collection-type.test.api.js +++ b/api-tests/core/content-type-builder/collection-type.test.api.js @@ -7,6 +7,7 @@ const { createStrapiInstance } = require('api-tests/strapi'); const { createAuthRequest } = require('api-tests/request'); const modelsUtils = require('api-tests/models'); +const { createTestBuilder } = require('api-tests/builder'); let strapi; let rq; @@ -17,8 +18,28 @@ const restart = async () => { rq = await createAuthRequest({ strapi }); }; +const builder = createTestBuilder(); + +const localTestData = { + models: { + dog: { + singularName: 'dog', + pluralName: 'dogs', + collectionName: 'dogs-collection', + displayName: 'Dog Display', + kind: 'collectionType', + attributes: { + name: { + type: 'string', + }, + }, + }, + }, +}; + describe('Content Type Builder - Content types', () => { beforeAll(async () => { + await builder.addContentType(localTestData.models.dog).build(); strapi = await createStrapiInstance(); rq = await createAuthRequest({ strapi }); }); @@ -40,6 +61,7 @@ describe('Content Type Builder - Content types', () => { await modelsUtils.deleteContentTypes(modelsUIDs, { strapi }); await strapi.destroy(); + await builder.cleanup(); }); describe('Collection Types', () => { @@ -92,7 +114,7 @@ describe('Content Type Builder - Content types', () => { expect(res.body).toMatchSnapshot(); }); - test('Successfull creation of a collection type with draftAndPublish enabled', async () => { + test('Successful creation of a collection type with draftAndPublish enabled', async () => { const res = await rq({ method: 'POST', url: '/content-type-builder/content-types', @@ -129,6 +151,54 @@ describe('Content Type Builder - Content types', () => { expect(res.body).toMatchSnapshot(); }); + test.each([ + ['singularName', 'singularName'], + ['singularName', 'pluralName'], + ['pluralName', 'singularName'], + ['pluralName', 'pluralName'], + ['pluralName', 'collectionName'], + ])(`Cannot use %p that exists as another type's %p`, async (sourceField, matchField) => { + const body = { + contentType: { + displayName: 'Frogs Frogs Frogs', + pluralName: 'safe-plural-name', + singularName: 'safe-singular-name', + collectionName: 'safe-collection-name', + attributes: { + name: { + type: 'string', + }, + }, + }, + }; + + // set the conflicting name in the given field + body.contentType[sourceField] = localTestData.models.dog[matchField]; + + const res = await rq({ + method: 'POST', + url: '/content-type-builder/content-types', + body, + }); + + expect(res.statusCode).toBe(400); + expect(res.body).toEqual({ + error: { + details: { + errors: [ + { + message: `contentType: name \`${body.contentType[sourceField]}\` is already being used by another content type.`, + name: 'ValidationError', + path: ['contentType', sourceField], + }, + ], + }, + message: `contentType: name \`${body.contentType[sourceField]}\` is already being used by another content type.`, + name: 'ValidationError', + }, + }); + }); + test('Cannot use same string for singularName and pluralName', async () => { const res = await rq({ method: 'POST', @@ -192,21 +262,11 @@ describe('Content Type Builder - Content types', () => { name: 'ValidationError', path: ['contentType', 'displayName'], }, - { - message: 'Content Type name `undefined` is already being used.', - name: 'ValidationError', - path: ['contentType', 'singularName'], - }, { message: 'contentType.singularName is a required field', name: 'ValidationError', path: ['contentType', 'singularName'], }, - { - message: 'Content Type name `undefined` is already being used.', - name: 'ValidationError', - path: ['contentType', 'pluralName'], - }, { message: 'contentType.pluralName is a required field', name: 'ValidationError', @@ -219,7 +279,7 @@ describe('Content Type Builder - Content types', () => { }, ], }, - message: '6 errors occurred', + message: '4 errors occurred', name: 'ValidationError', }, }); diff --git a/api-tests/core/database/transactions.test.api.js b/api-tests/core/database/transactions.test.api.js index 639486f725..7b8ad2aff0 100644 --- a/api-tests/core/database/transactions.test.api.js +++ b/api-tests/core/database/transactions.test.api.js @@ -225,6 +225,27 @@ describe('transactions', () => { expect(end[0].key).toEqual(original[0].key); }); + + test('onCommit hook works', async () => { + let count = 0; + await strapi.db.transaction(({ onCommit, onRollback }) => { + onCommit(() => count++); + }); + expect(count).toEqual(1); + }); + + test('onRollback hook works', async () => { + let count = 0; + try { + await strapi.db.transaction(({ onRollback }) => { + onRollback(() => count++); + throw new Error('test'); + }); + } catch (e) { + // do nothing + } + expect(count).toEqual(1); + }); }); describe('using a transaction object', () => { diff --git a/api-tests/core/upload/admin/folder-file.test.api.js b/api-tests/core/upload/admin/folder-file.test.api.js index 2b3e82d43b..b299a88a1e 100644 --- a/api-tests/core/upload/admin/folder-file.test.api.js +++ b/api-tests/core/upload/admin/folder-file.test.api.js @@ -220,6 +220,34 @@ describe('Bulk actions for folders & files', () => { const existingfoldersIds = resFolder.body.data.map((f) => f.id); expect(existingfoldersIds).toEqual(expect.not.arrayContaining([folder.id])); }); + + test('Can delete folders without deleting others with similar path', async () => { + // Ensure the folder algo does not only delete by folders that start with the same path (startswith /1 matches /10 too) + + // Delete all previous folders, so first file path is /1 + await rq + .get('/upload/folders') + .then((res) => res.body.data.map((f) => f.id)) + .then((folderIds) => rq.post('/upload/actions/bulk-delete', { body: { folderIds } })); + + // Create folders + const folder1 = await createFolder('folderToDelete', null); + for (let i = 0; i < 20; i++) { + await createFolder(`folderToKeep-${i}`, null); + } + // Delete folder1 + await rq.post('/upload/actions/bulk-delete', { + body: { folderIds: [folder1.id] }, + }); + + const folderIds = await rq + .get('/upload/folders') + .then((res) => res.body.data.map((f) => f.id)); + + // Should include all folders except the one we deleted + expect(folderIds.length).toBe(20); + expect(folderIds).toEqual(expect.not.arrayContaining([folder1.id])); + }); }); describe('move', () => { diff --git a/docs/.eslintrc.docs.js b/docs/.eslintrc.docs.js new file mode 100644 index 0000000000..11a4680ce0 --- /dev/null +++ b/docs/.eslintrc.docs.js @@ -0,0 +1,9 @@ +module.exports = { + parserOptions: { + sourceType: 'module', + }, + plugins: ['react'], + rules: { + 'react/prop-types': 0, + }, +}; diff --git a/docs/docs/api/Strapi.mdx b/docs/docs/api/Strapi.mdx new file mode 100644 index 0000000000..3f6c457174 --- /dev/null +++ b/docs/docs/api/Strapi.mdx @@ -0,0 +1,598 @@ +--- +title: Strapi (WIP) +slug: /api/Strapi +tags: +- class +- public +- global + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- +import Type from '@site/docs/api/components/type'; + +# Strapi + +:::info + +Current state: **Stable** + +::: + +The Strapi class is the main object used in Strapi projects. +An instance of Strapi class is available as a global in any Strapi project: `global.strapi`. + +## Class: Strapi + +### `new Strapi(opts)` + +- `opts`: Object Options that can be used on Strapi startup + - `autoReload`: Boolean **Default:** true + - If false, deactivate auto reload + - If you modify any file in your Strapi project, it reloads your nodejs app + - If any content-type is changed, it will reload the nodejs app + - `serveAdminPanel`: Boolean **Default:** true + - Should the admin panel be loaded and serve as a web client + - The admin panel build will not be delivered if false + - `appDir`: String **Default:** `process.cwd()` + - The directory relative or absolute path where Strapi will write every file (schemas, generated APIs, controllers or services) + - `distDir`: String **Default:** appDir value + - The directory relative or absolute path where Strapi will read configurations, schemas and any compiled code + +Instances of the Strapi class can be created using the new keyword. + +```javascript +const strapiInstance = new Strapi(); +``` + +### `strapi.container` +- [Container](./container) + +The container provides a simple and efficient way to register and manage resources, making it easy to access and use them throughout the application. +By registering a registry with the container, it can be easily retrieved by other parts of the application, making it a powerful tool for organizing and reusing code across the entire codebase. + +See [Container](./container). + +### `strapi.dirs` +- Object + +Stored paths of file system. + +- `dirs.dist`: [StrapiPathObject](#strapipathobject) + - Build folder +- `dirs.app`: [StrapiPathObject](#strapipathobject) + - Sources folder +- `dirs.static`: Object Define path to directories involving web client display + - `public`: String Path to the folder to serve publicly (like files, images, etc..) + +#### StrapiPathObject +- Object + +A set of paths to specific Strapi project parts. + +- `root`: String Root path +- `src`: String Sources route path to project files +- `api`: String Path to the folder containing project developers' API files (content-types, controllers, services, routes, etc..) +- `components`: String Path to the folder containing project developers' components +- `policies`: String Path to the folder where the Strapi project developers' policies are stored + - A set of functions that check the state of the data and prevent the access to the API accordingly +- `middlewares`: String Path to the folder where the Strapi project developers' middlewares are stored + - A set of function that wrap around routes and requests +- `config`: String Path to the folder containing project developers' config files + +### `strapi.isLoaded` +- Boolean + + - `true`: Everything (all `register` and `bootstrap` functions available in your strapi project) has been loaded + - `false`: There is something loading + +Note: `register` functions are called before the `bootstrap` functions. + +### `strapi.reload()` + +Reload the app. + +This function defines itself at the construction of the Strapi class. + +### `strapi.server` +- [StrapiServer](./strapi-server) + +Strapi server object. + +### `strapi.fs` +- [StrapiFS](StrapiFS) + +Wrapper around [FS NodeJS module](https://nodejs.org/docs/latest-v18.x/api/fs.html). + +### `strapi.eventHub` +- [EventHub](EventHub) + +The `strapi.eventHub` object is used to manipulate events within a Strapi project. It is an instance of the built-in EventEmitter class from Node.js, which provides a simple way to emit and listen for events. + +The `strapi.eventHub` object is created using the `createEventHub()` function in the [EventHub](EventHub) module of the Strapi core. This function returns a new instance of the EventHub class, which extends the EventEmitter class and adds some additional functionality specific to Strapi. + + +#### Examples: + +```javascript +// Listen for a 'user.updated' event and log the data +strapi.eventHub.on('user.updated', (data) => { +console.log(`User ${data.id} has been updated`); +}); + +// Emit a 'user.created' event with some data +strapi.eventHub.emit('user.created', { username: 'johndoe', email: 'johndoe@example.com' }); +``` +In this example, we are emitting a `user.created` event with some data attached to it, and then listening for a user.updated event and logging the data. These events can be used to trigger actions within the Strapi application or to communicate with external systems. + +For more information on how to use the EventEmitter class and its methods, see the [Node.js documentation](ttps://nodejs.org/docs/latest-v18.x/api/events.html#class-eventemitter). + +### `strapi.startupLogger` +- [StartupLogger](StartupLogger) + +Object containing predefined logger functions. Used for Strapi startup. (do not use as a logger elsewhere) + +### `strapi.log` +- [Winston](https://github.com/winstonjs/winston#creating-your-own-logger) + +A logger provided by Strapi that uses the Winston logging library. It is the result of calling the `winston.createLogger()` function with the configuration defined by the user of the Strapi application. + +The logger provides various methods for logging messages at different levels of severity, including error, warn, info, verbose, debug, and silly. The logging level can be set via the configuration to control which messages are logged. + +#### Examples +```javascript +// Log an error message +strapi.log.error('Failed to start server', { error: err }); + +// Log a warning message +strapi.log.warn('Server is running in development mode'); + +// Log an informational message +strapi.log.info(`Server started on port ${PORT}`); + +// Log a verbose message +strapi.log.verbose('Application state', { user: currentUser }); + +// Log a debug message +strapi.log.debug('API request received', { method: req.method, path: req.path }); + +// Log a silly message +strapi.log.silly('Entered loop', { count: i }); +``` +In these examples, we are logging messages at different levels of severity, including error, warn, info, verbose, debug, and silly. We are also passing in metadata as an object in the second parameter of each logging method. + +The messages logged by strapi.log will be output according to the logging configuration set by the user of the Strapi application. This configuration determines which messages are logged and where they are logged (e.g. console, file, etc.). + +### `strapi.cron` +- [CronService](Cron) + +Module to schedule cron jobs for Strapi project. It is an instance of a custom Cron object. + +### `strapi.telemetry` +- [TelemetryService](Telemetry) + +The `strapi.telemetry` property provides access to the telemetry service instance. This service collects anonymous usage data about your Strapi application to help the Strapi team improve the product. + +By default, the telemetry service is enabled, but you can disable it by setting the telemetryDisabled property to true in your application's package.json file, or by setting the `STRAPI_TELEMETRY_DISABLED` environment variable to true. You can also disable telemetry programmatically by setting the isDisabled property of the `strapi.telemetry` instance to true. + +### `strapi.requestContext` +- Object Context Storage + + - `run(store, cb)`: Function + - `store`: Any Value that should be retrieved + - `cb`: Function Callback + - `get()` Function + +The request context stores the ctx object from KoaJS on each request. This allows users to have access to the context from anywhere through the Strapi instance. + +### `strapi.customFields` +- Object + + - `register(customField)`: Function Register a new custom field + +This property is a shortcut to `strapi.container.get('custom-fields').add(customField)`. + +#### Examples +```javascript + strapi.customFields.register({ + name: 'color', + plugin: 'color-picker', + type: 'string', + }); +``` + +### `strapi.config` +- Object + +Shortcut to `strapi.container.get('config')`. + +See the [config container](#config). + +### `strapi.services` +- Object[] + +Shortcut to `strapi.container.get('services').getAll()`. + +See the [services' container](#services). + +### `strapi.service(uid)` +- `uid`: String + +Shortcut to `strapi.container.get('services').get(uid)`. + +See the [services' container](#services). + +### `strapi.controllers` +- Object[] + +Shortcut to `strapi.container.get('controllers').getAll()`. + +See the [controllers' container](#controllers). + +### `strapi.controller(uid)` +- `uid`: String + +Shortcut to `strapi.container.get('controllers').get(uid)`. + +See the [controllers' container](#controllers). + +### `strapi.contentTypes` +- Object[] + +Shortcut to `strapi.container.get('content-types').getAll()`. + +See the [content-types' container](#content-types). + +### `strapi.contentType(name)` +- `name`: String + +Shortcut to `strapi.container.get('content-types').get(name)`. + +See the [content-types' container](#content-types). + +### `strapi.policies` +- Object[] + +Shortcut to `strapi.container.get('policies').getAll()`. + +See the [policies' container](#policies). + +### `strapi.policy(name)` +- `name`: String + +Shortcut to `strapi.container.get('policies').get(name)`. + +See the [policies' container](#policies). + +### `strapi.middlewares` +- Object[] + +Shortcut to `strapi.container.get('middlewares').getAll()`. + +See the [middlewares container](#middlewares). + +### `strapi.middleware(name)` +- `name`: String + +Shortcut to `strapi.container.get('middlewares').get(name)`. + +See the [middlewares container](#middlewares). + +### `strapi.plugins` +- Object[] + +Shortcut to `strapi.container.get('plugins').getAll()`. + +See the [plugins' container](#plugins). + +### `strapi.plugin(name)` +- `name`: String + +Shortcut to `strapi.container.get('plugins').get(name)`. + +See the [plugins' container](#plugins). + +### `strapi.hooks` +- Object[] + +Shortcut to `strapi.container.get('hooks').getAll()`. + +See the [hooks' container](#hooks). + +### `strapi.hook(name)` +- `name`: String + +Shortcut to `strapi.container.get('hooks').get(name)`. + +See the [hooks' container](#hooks). + +### `strapi.api` +- Object[] + +Shortcut to `strapi.container.get('apis').getAll()`. + +See the [apis container](#apis). + +### `strapi.auth` +- Object + +Shortcut to `strapi.container.get('auth')`. + +See the [auth' container](#auth). + +### `strapi.contentAPI` +- Object + +Shortcut to `strapi.container.get('content-api')`. + +See the [content-api container](#content-api). + +### `strapi.sanitizers` +- Object + +Shortcut to `strapi.container.get('sanitizers')`. + +See the [sanitizers' container](#sanitizers). + +### `strapi.start()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.destroy()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.sendStartupTelemetry()` + +:::info +TODO +::: + +### `strapi.openAdmin({ isInitialized })` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.postListen()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.listen()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.stopWithError()` + +:::info +TODO +::: + +### `strapi.stop(exitCode)` + +:::info +TODO +::: + +### `strapi.loadAdmin()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.loadPlugins()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.loadPolicies()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.loadAPIs()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.loadComponents()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.loadMiddlewares()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.loadApp()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.loadSanitizers()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.registerInternalHooks()` + +:::info +TODO +::: + +### `strapi.register()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.bootstrap()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.load()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.startWebhooks()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.reload()` + +:::info +TODO +::: + +### `strapi.runLifecyclesFunctions()` +- Returns: Promise + +:::info +TODO +::: + +### `strapi.getModel(uid)` +- `uid`: String + +:::info +TODO +::: + +### `strapi.query(uid)` +- `uid`: String + +:::info +TODO +::: + +## Strapi containers + +The strapi containers are accessible via `strapi.container.get('name-of-the-container')`. + +### `config` +- Object + + - `get(path, defaultValue)`: Function + - `path`: String + - `defaultValue`: Any + - Returns: Any - The value located at `path` or, if undefined, `defaultValue`. + - `set(path, value)`: Function + - `path`: String - Where the value should be stored + - `value`: Any + - `has(path)`: Function + - `path`: String + - Returns: Boolean - Does the `path` match a value stored in the config container. + - `launchedAt`: Number **Default:** `Date.now()` + Date in milliseconds when the server has started + - `serveAdminPanel`: Boolean **Default:** `true` + See [Strapi constructor](#new-strapiopts) options + - `autoReload`: Boolean **Default:** `false` + See [Strapi constructor](#new-strapiopts) options + - `environment`: String - process.env.NODE_ENV + - `uuid`: String - string extracted from `package.json` located in `strapi.uuid` + - `packageJsonStrapi`: Object - object extracted from `package.json` located in `strapi` (except uuid) + - `info`: Object + - everything stored in the `package.json` + - `strapi`: String - Current version of Strapi + +Every file stored under the `config` folder will be injected in this config container object. + +### `services` + +:::info +TODO +::: + +### `controllers` + +:::info +TODO +::: + +### `content-types` + +:::info +TODO +::: + +### `policies` + +:::info +TODO +::: + +### `plugins` + +:::info +TODO +::: + +### `hooks` + +:::info +TODO +::: + +### `apis` + +:::info +TODO +::: + +### `auth` + +:::info +TODO +::: + +### `content-api` + +:::info +TODO +::: + +### `sanitizers` + +:::info +TODO +::: + diff --git a/docs/docs/api/api.mdx b/docs/docs/api/api.mdx new file mode 100644 index 0000000000..52f9eb66a6 --- /dev/null +++ b/docs/docs/api/api.mdx @@ -0,0 +1,29 @@ +--- +title: API (WIP) +slug: /api/API +tags: +- module +- public + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- +import Type from '@site/docs/api/components/type'; + +# API + +:::info + +Current state: **Stable** + +::: + +The Strapi API module permits to generate a Strapi API object that wrap all the functionalities around Strapi endpoints + +## Module: API + +### `createAPI(strapi, opts)` + +:::info +TODO +::: diff --git a/docs/docs/api/components/type.jsx b/docs/docs/api/components/type.jsx new file mode 100644 index 0000000000..e1f2c5e7b6 --- /dev/null +++ b/docs/docs/api/components/type.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export default function Type({ children }) { + return ( + + <{children}> + + ); +} diff --git a/docs/docs/api/container.mdx b/docs/docs/api/container.mdx new file mode 100644 index 0000000000..4d3884999f --- /dev/null +++ b/docs/docs/api/container.mdx @@ -0,0 +1,102 @@ +--- +title: Container +slug: /api/container +tags: +- module +- public + +toc_min_heading_level: 2 +toc_max_heading_level: 5 +--- +import Type from '@site/docs/api/components/type'; + +# Container + +:::info + +Current state: **Stable** + +::: + +The container module permits to generate containers. + +## Module: container + +### `createContainer(strapi)` + +- `strapi`: Strapi [See Strapi class documentation](Strapi.mdx) +- Returns: Container + +```javascript +const container = createContainer(strapi); + +container.register('config', { + get: (configName) => {}, + set: (configName, value) => {} +}); + +const dbConfig = container.get('config').get('database'); +``` + +### `container.register(name, resolver)` + +- `name`: String UID of the content +- `resolver`: Function | Any + - As a function, the function will be executed when the first get method is called on this content. The result of this function will define the content of this UID. + - `resolver(context, args)` + - `context`: { Strapi } [See Strapi class documentation](Strapi.mdx) + - `args`: Any Anything to be used by the resolver function + - As anything else, this value will be resolved when getting this specified content through its UID. + +Register a new content to be accessed inside the container. If the name is already used, it will throw an error. + +```javascript +const container = createContainer(strapi); + +container.register('config', ({ strapi }, args) => {}); +// or +container.register('services', {}); +``` + +### `container.get(name, args)` + +- `name`: String UID of the content +- `args`: Any Value that will be passed to the resolver (if function) + +Get the value stored for a specific `name`. + +```javascript +const container = createContainer(strapi); + +container.register('config', { db: 'sqlite' }); + +const config = container.get('config'); +// config.db === 'sqlite' +``` + +⚠️ If the **resolver**, used in the [register function](#containerregistername-resolver), is a **function**, the value will be the result of this resolver function with `args` as parameter on the first call to `get`. + +Please pay attention that the resolver result value isn't awaited. So if resolver returns a promise, the value stored will be a promise. + +```javascript +const container = createContainer(strapi); + +container.register('boolean', (bool) => bool); + +// First call - The value is resolved through the resolver above "(bool) => bool" +container.get('boolean', true); +// true + +// Any further call will use the previously set value +container.get('boolean'); +// true + +// Even if we try to push a new value +container.get('boolean', false); +// true +``` +### `container.extend()` + +:::info + To be developed +::: diff --git a/docs/docs/api/cron.mdx b/docs/docs/api/cron.mdx new file mode 100644 index 0000000000..5c12116cbf --- /dev/null +++ b/docs/docs/api/cron.mdx @@ -0,0 +1,195 @@ +--- +title: Cron Service +slug: /api/Cron +tags: +- module +- public + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- +import Type from '@site/docs/api/components/type'; + +# Cron + +:::info + +Current state: **Stable** + +::: + +The Strapi Cron Service provides a way to add, remove, start, and stop cron jobs in a Strapi application. + + +## Module: Cron Service + +### createCronService() + +The `createCronService()` function returns an object that provides methods to manage cron jobs. + +## Methods + +### `cron.add(tasks)` + +- `tasks`: Object +- Returns: `this` + +Adds one or more cron tasks to the service. + +- Each key of the `tasks` object is the name of the task. +- Each value of the `tasks` object can be either a function, or an object with two properties: `task` and `options`. +- If the value is a function, it is used as the task to be executed when the cron expression is met. + - The key will be considered as the cron expression +- If the value is an object, its `task` property is used as the task function, and its `options` property is used as the cron expression options. + +#### Example +```javascript +const { createCronService } = require('packages/core/strapi/lib/services/cron.js'); + +const cron = createCronService(); + +const task = () => { + console.log('Task executed!'); +}; + +cron.add({ + myTask: { + task, + options: '*/5 * * * *', // Executes every 5 minutes. + }, + '*/1 * * * *': () => console.log('A minute has passed.'), +}); +``` + +### `cron.remove(name)` + +- `name`: String +- Returns: `this` + +Removes a cron task from the service. + +- The `name` parameter is the name of the task to remove. + +#### Example +```javascript +const { createCronService } = require('packages/core/strapi/lib/services/cron.js'); + +const cron = createCronService(); + +const task = () => { + console.log('Task executed!'); +}; + +cron.add({ + myTask: { + task, + options: '*/5 * * * *', // Executes every 5 minutes. + }, +}); + +cron.remove('myTask'); +``` + +### `cron.start()` + +- Returns: `this` + +Starts the cron service. + +- Schedules all the cron jobs. + +#### Example +```javascript +const { createCronService } = require('packages/core/strapi/lib/services/cron.js'); + +const cron = createCronService(); + +const task = () => { + console.log('Task executed!'); +}; + +cron.add({ + myTask: { + task, + options: '*/5 * * * *', // Executes every 5 minutes. + }, +}); + +cron.start(); +``` + +### `cron.stop()` + +- Returns: `this` + +Stops the cron service. + +- Cancels all the scheduled jobs. + +#### Example +```javascript +const { createCronService } = require('packages/core/strapi/lib/services/cron.js'); + +const cron = createCronService(); + +const task = () => { + console.log('Task executed!'); +}; + +cron.add({ + myTask: { + task, + options: '*/5 * * * *', // Executes every 5 minutes. + }, +}); + +// Start the scheduled cron jobs +cron.start(); +// Stops the cron jobs +cron.stop(); +``` + +### `cron.destroy()` + +- Returns: `this` + +Destroys the cron service. + +- Calls the `stop()` method. +- Clears the list of cron jobs. + +#### Example +```javascript +const { createCronService } = require('packages/core/strapi/lib/services/cron.js'); + +const cron = createCronService(); + +const task = () => { + console.log('Task executed!'); +}; + +cron.add({ + myTask: { + task, + options: '*/5 * * * *', // Executes every 5 minutes. + }, +}); + +// Start the scheduled cron jobs +cron.start(); +// Stops the cron jobs and remove all scheduled tasks +cron.destroy(); +``` + +## Properties + +### `cron.jobs` +- Array + + - Object + + - `job`: [Job](https://github.com/node-schedule/node-schedule) - Job object by node-schedule + - `options`: String - String representing the recurrence of the job ( like '*/5 * * * *' ) + - `name`: String - The name of the task associated to the job + +An array of the cron jobs added to the service. diff --git a/docs/docs/api/event-hub.mdx b/docs/docs/api/event-hub.mdx new file mode 100644 index 0000000000..aa86958594 --- /dev/null +++ b/docs/docs/api/event-hub.mdx @@ -0,0 +1,29 @@ +--- +title: EventHub (WIP) +slug: /api/EventHub +tags: +- module +- public + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- +import Type from '@site/docs/api/components/type'; + +# Strapi Event Hub + +:::info + +Current state: **Stable** + +::: + +Strapi Event Hub module - description to be done + +## Module: EventHub + +### `createEventHub()` + +:::info +TODO +::: diff --git a/docs/docs/api/index.md b/docs/docs/api/index.md deleted file mode 100644 index 593279293f..0000000000 --- a/docs/docs/api/index.md +++ /dev/null @@ -1 +0,0 @@ -# API diff --git a/docs/docs/api/startup-logger.mdx b/docs/docs/api/startup-logger.mdx new file mode 100644 index 0000000000..9939c38764 --- /dev/null +++ b/docs/docs/api/startup-logger.mdx @@ -0,0 +1,72 @@ +--- +title: Startup Logger +slug: /api/StartupLogger +tags: +- module +- private + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- +import Type from '@site/docs/api/components/type'; + +# API + +:::info + +Current state: **Stable** + +::: + +This module is a simple logger for starting up Strapi with some useful information. + +## Module: Startup Logger + +### `logStats()` + +This log will display information about the instance of Strapi. The time launched, how many times it took and important configuration information. + +``` + Project information + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Time β”‚ Wed Jan 01 2000 00:00:01 GMT+0200 (Central Euro… β”‚ +β”‚ Launched in β”‚ 2000 ms β”‚ +β”‚ Environment β”‚ development β”‚ +β”‚ Process PID β”‚ 42 β”‚ +β”‚ Version β”‚ 4.9.0 (node v18.12.1) β”‚ +β”‚ Edition β”‚ Enterprise β”‚ +β”‚ Database β”‚ postgres β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### `logFirstStartupMessage()` + +This log will display the first time Strapi project is launched. It will ask the user to create its first admin user in the admin panel. + +### `logDefaultStartupMessage()` + +Default message to display when the Strapi server is started. + +``` + Actions available + +Welcome back! +To manage your project πŸš€, go to the administration panel at: +http://localhost:1337/admin + +To access the server ⚑️, go to: +http://localhost:1337 +``` + +### `logStartupMessage({ isInitialized })` + +- `isInitialized`: Boolean Has the Strapi project already been initialized? + +Will display the correct start-up message according to the specified boolean. + +:::note + +Can be disabled by setting `STRAPI_HIDE_STARTUP_MESSAGE` to `true`. + +::: diff --git a/docs/docs/api/strapi-fs.mdx b/docs/docs/api/strapi-fs.mdx new file mode 100644 index 0000000000..8695482e06 --- /dev/null +++ b/docs/docs/api/strapi-fs.mdx @@ -0,0 +1,42 @@ +--- +title: StrapiFS (WIP) +slug: /api/StrapiFS +tags: +- module +- public + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- +import Type from '@site/docs/api/components/type'; + +# Strapi File System + +:::info + +Current state: **Stable** + +::: + +The Strapi FS module is a wrapper around FS NodeJS module to manipulate local files. + +## Module: StrapiFS + +### `createStrapiFs(strapi)` + +### `strapiFs.writeAppFile(optPath, data)` +:::caution +Deprecated +::: + +### `strapiFs.writePluginFile(plugin, optPath, data)` +:::caution +Deprecated +::: + +### `strapiFs.removeAppFile(optPath)` +:::caution +Deprecated +::: + +### `strapiFs.appendFile(optPath, data)` diff --git a/docs/docs/api/strapi-server.mdx b/docs/docs/api/strapi-server.mdx new file mode 100644 index 0000000000..e0ab038814 --- /dev/null +++ b/docs/docs/api/strapi-server.mdx @@ -0,0 +1,117 @@ +--- +title: Strapi server (WIP) +slug: /api/strapi-server +tags: +- module +- public + +toc_min_heading_level: 2 +toc_max_heading_level: 5 +--- +import Type from '@site/docs/api/components/type'; + +# Strapi Server + +:::info + +Current state: **Stable** + +::: + +The Strapi server module permits to generate a Strapi http server. + +## Module: Strapi server + +### `createServer(strapi)` + +- `strapi`: [Strapi](Strapi) +- Returns: StrapiServer + +```javascript +const server = createServer(strapi); + +server.listRoutes(); +``` + +### `StrapiServer.app` + +- [KoaJS](https://devdocs.io/koa/index) + +Strapi projects are using KoaJS to run the NodeJS server. + +### `StrapiServer.router` + +- [@koa/router](https://github.com/ZijianHe/koa-router#router-) + +Strapi projects are using a dependency of KoaJS called @koa/router. + +### `StrapiServer.httpServer` + +- [http.Server](https://nodejs.org/docs/latest-v18.x/api/http.html) + +The Strapi's HTTP server. + +### `StrapiServer.api(name)` + +- `name`: String +- Returns: [StrapiAPIs](#strapiapis) + +Getter for apis available in Strapi + +### `StrapiServer.use(...args)` + +- [KoaApp.use](https://devdocs.io/koa/index#appusefunction) + +Shortcut for Koa `app.use(...args)` method. + +### `StrapiServer.routes(routes)` + +:::info +TODO +::: + +### `StrapiServer.mount()` + +:::info +TODO +::: + +### `StrapiServer.initRouting()` + +:::info +TODO +::: + +### `StrapiServer.initMiddlewares()` + +:::info +TODO +::: + +### `StrapiServer.listRoutes()` + +:::info +TODO +::: + +### `StrapiServer.listen(...args)` + +:::info +TODO +::: + +### `StrapiServer.destroy()` + +:::info +TODO +::: + +### `StrapiAPIs` +- Object + +- `content-api`: [API](API) + - API used by external requesters +- `admin`: [API](API) + - API used by admin panel + +Strapi APIs is a map of all APIs available inside the Strapi project. diff --git a/docs/docs/api/structure.example b/docs/docs/api/structure.example new file mode 100644 index 0000000000..2e603cbd5b --- /dev/null +++ b/docs/docs/api/structure.example @@ -0,0 +1,93 @@ +--- +title: API Reference example +tags: + - utils + - class + - public + - global + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- + +import Type from '@site/docs/api/components/type'; + +# Name of Module + +:::info + +Current state: **Stable** | **Legacy** | **Deprecated** + +::: + +_**Stable** - can be use as is_ + +_**Legacy** - Old code that needs refactoring to match the current architecture of the code_ + +_**Deprecated** - Should **NOT** be used, this will be deleted anytime soon_ + +_Description with a general example on how to use this Class / Module_ + +## Class: Name of the class + +### Public variable (e.g. in an EventEmitter class `Event: 'close'`) + +### Static methods (e.g. `Static method: Class.default()`) + +### `new Class()` + +Instances of the Class class can be created using the new keyword. + +```javascript +const myClass = new Class(); +``` + +### `class.method(param1, param2)` + +- `param1`: String (can be linked to other API doc page). +- `param2`: Object + - `options1`: Number + +The `class.method()` method display the `param1` and then skip `param2` lines. + +#### Examples + +```javascript +const { Class } = require('pathToClassFile'); +const textLines = ['Welcome', "That's all", 'Thanks']; + +const classInstance = new Class(); + +for (const text of textLines) { + classInstance.method(text, 1); +} +// Prints: +// Welcome +// That's all +// Thanks +``` + +## Function: `name_of_the_function(param1, param2)` + +- `param1`: String (can be linked to other API doc page) +- `param2`: Object + - `options1`: Number + +The `name_of_the_function()` method display the `param1` and then skip `param2` lines. + +#### Examples + +```javascript +const { name_of_the_function } = require('pathToFunctionFile'); +const textLines = ['Welcome', "That's all", 'Thanks']; + +for (const text of textLines) { + name_of_the_function(text, 1); +} +// Prints: +// Welcome +// That's all +// Thanks +``` + +This structure is highly based on NodeJS API reference documentation. https://nodejs.org/api diff --git a/docs/docs/api/telemetry.mdx b/docs/docs/api/telemetry.mdx new file mode 100644 index 0000000000..8f07aba17f --- /dev/null +++ b/docs/docs/api/telemetry.mdx @@ -0,0 +1,102 @@ +--- +title: Telemetry Service +slug: /api/Telemetry +tags: +- module +- public + +toc_min_heading_level: 2 +toc_max_heading_level: 3 +--- +import Type from '@site/docs/api/components/type'; + +# Telemetry + +:::info + +Current state: **Stable** + +::: + +The telemetry service is responsible for collecting and sending anonymous usage data to Strapi. This service is disabled by default, but can be enabled or disabled via configuration. + +## Usage Information +The collected usage data is used to help Strapi improve the product by identifying areas of improvement, tracking feature adoption, and measuring performance. You can learn more about the usage data that is collected by visiting the following link: + +https://docs.strapi.io/developer-docs/latest/getting-started/usage-information.html + +## Module: Telemetry Service + +### createTelemetryInstance() + +- strapi: [Strapi](Strapi) - A strapi instance. + +The `createTelemetryInstance()` function returns an instance of the Telemetry service. + +#### Examples + +```javascript +const createTelemetryInstance = require('path/to/telemetry'); + +const telemetry = createTelemetryInstance(strapi); +``` + +## Methods + +### `telemetry.register()` + +Registers the telemetry instance. + +#### Examples + +```javascript +telemetry.register(); +``` + +### `telemetry.bootstrap()` + +Bootstraps the telemetry instance. + +#### Examples + +```javascript +telemetry.bootstrap(); +``` + +### `telemetry.destroy()` + +Destroys the telemetry instance. + +#### Examples + +```javascript +telemetry.destroy(); +``` + +### `telemetry.send(event, payload)` +- `event`: String - The event to be sent. +- `payload`: [TelemetryPayload](#telemetrypayload) - The payload to be sent with the event. +- Returns: Promise + +Sends telemetry event with the given payload. + +#### Examples + +```javascript +telemetry.send('event_name', { key: 'value' }); +``` + +## Types + +### `TelemetryPayload` +- Object + + - `eventProperties`: Object An object that contains additional information about the event. + - `userProperties`: Object An object that defines the identity of the user who triggered the event. + - `groupProperties`: Object An object that defines properties of the application or environment in which the event occurred. + +Examples of event properties in Strapi include model, containsRelationalFields, displayedFields, kind, and hasDraftAndPublish. These properties are specific to the event and are used to provide additional context about what happened. + +User properties can include information such as the user's operating system, node version, and hostname. These properties are typically used to group events by user or to filter events based on certain user characteristics. + +Group properties can include information such as the language(s) used in the application, the database being used, and the number of locales. These properties are typically used to group events by application version, environment, or other characteristics. \ No newline at end of file diff --git a/docs/docs/docs/00-intro.md b/docs/docs/docs/00-intro.md new file mode 100644 index 0000000000..3c71093914 --- /dev/null +++ b/docs/docs/docs/00-intro.md @@ -0,0 +1,20 @@ +--- +title: Introduction +--- + +Hello & welcome to the contributor documentation of the Strapi Monorepo! Here you'll find both technical and conceptual documentation on the codebase. + +Generally speaking the documentation structure is as follows: + +```shell +core/ +β”œβ”€ admin/ +β”œβ”€ content-manager/ +β”‚ β”œβ”€ documentation-file.mdx +β”œβ”€ content-type-builder/ +plugins/ +β”œβ”€ documentation/ +β”œβ”€ i18n/ +``` + +This helps keep the documentation organised according to the file structure of the `packages` folder within the monorepo. From there however, is dependant on the documentation written, it will most likely change over time when the documentation grows and develops. diff --git a/docs/docs/docs/01-core/_category_.json b/docs/docs/docs/01-core/_category_.json new file mode 100644 index 0000000000..9a6bf863f3 --- /dev/null +++ b/docs/docs/docs/01-core/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "label": "Core", + "collapsible": true, + "collapsed": false +} diff --git a/docs/docs/docs/01-core/admin/00-intro.md b/docs/docs/docs/01-core/admin/00-intro.md new file mode 100644 index 0000000000..eeab250bdf --- /dev/null +++ b/docs/docs/docs/01-core/admin/00-intro.md @@ -0,0 +1,16 @@ +--- +title: Introduction +tags: + - admin +--- + +# Admin + +This section is an overview of all the features related to admin: + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import { useCurrentSidebarCategory } from '@docusaurus/theme-common'; + + +``` diff --git a/docs/docs/docs/01-core/admin/01-ee/00-intro.md b/docs/docs/docs/01-core/admin/01-ee/00-intro.md new file mode 100644 index 0000000000..76733cb150 --- /dev/null +++ b/docs/docs/docs/01-core/admin/01-ee/00-intro.md @@ -0,0 +1,16 @@ +--- +title: Introduction +tags: + - enterprise-edition +--- + +# Admin Enterprise Edition + +This section is an overview of all the features related to the Enterprise Edition in Admin: + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import { useCurrentSidebarCategory } from '@docusaurus/theme-common'; + + +``` diff --git a/docs/docs/docs/01-core/admin/01-ee/01-review-workflows.md b/docs/docs/docs/01-core/admin/01-ee/01-review-workflows.md new file mode 100644 index 0000000000..b294097e47 --- /dev/null +++ b/docs/docs/docs/01-core/admin/01-ee/01-review-workflows.md @@ -0,0 +1,139 @@ +--- +title: Review Workflows +description: Review workflow technical design +tags: + - review-workflows + - implementation + - tech design +--- + +# Review Workflows + +## Summary + +The review workflow feature is only available in the Enterprise Edition. +That is why, in part, it is completely decoupled from the code of the Community Edition. + +The purpose of this feature is to allow users to assign a tag to the various entities of their Strapi project. This tag is called a 'stage' and is available within what we will call a workflow. + +## Detailed backend design + +The Review Workflow feature have been built with one main consideration, to be decoupled from the Community Edition. As so, the implementation can relate a lot to how a plugin would be built. + +All the backend code related to Review Workflow can be found in `packages/core/admin/ee`. +This code is separated into several elements: + +- Two content-types + - _strapi_workflows_: `packages/core/admin/ee/server/content-types/workflow/index.js` + - _strapi_workflows_stages_: `packages/core/admin/ee/server/content-types/workflow-stage/index.js` +- Two controllers + - _workflows_: `packages/core/admin/ee/server/controllers/workflows/index.js` + - _stages_: `packages/core/admin/ee/server/controllers/workflows/stages/index.js` +- One middleware + - _contentTypeMiddleware_: `packages/core/admin/ee/server/middlewares/review-workflows.js` +- Routes + - `packages/core/admin/ee/server/routes/index.js` +- Four services + - _review-workflows_: `packages/core/admin/ee/server/services/review-workflows/review-workflows.js` + - _workflows_: `packages/core/admin/ee/server/services/review-workflows/workflows.js` + - _stages_: `packages/core/admin/ee/server/services/review-workflows/stages.js` + - _metrics_: `packages/core/admin/ee/server/services/review-workflows/metrics.js` +- One decorator + - _EntityService_ decorator: `packages/core/admin/ee/server/services/review-workflows/entity-service-decorator.js` +- One utils file + - _Review workflows utils_: `packages/core/admin/ee/server/utils/review-workflows.js` +- A bootstrap and a register part + - `packages/core/admin/ee/server/bootstrap.js` + - `packages/core/admin/ee/server/register.js` + +### Content types + +#### strapi_workflows + +This content type stores the workflow information and is responsible for holding all the information about stages and their order. In MVP, only one workflow is stored inside the Strapi database. + +#### strapi_workflows_stages + +This content type store the stage information such as its name. + +### Controllers + +#### workflows + +Used to interact with the `strapi_workflows` content-type. + +#### stages + +Used to interact with the `strapi_workflows_stages` content-type. + +### Middlewares + +#### contentTypeMiddleware + +In order to properly manage the options for content-type in the root level of the object, it is necessary to relocate the `reviewWorkflows` option within the `options` object located inside the content-type data. By doing so, we can ensure that all options are consistently organized and easily accessible within their respective data structures. This will also make it simpler to maintain and update the options as needed, providing a more streamlined and efficient workflow for developers working with the system. Therefore, it is recommended to move the reviewWorkflows option to its appropriate location within the options object inside the content-type data before sending it to the admin API. + +### Routes + +The Admin API of the Enterprise Edition includes several routes related to the Review Workflow feature. Here is a list of those routes: + +#### GET `/review-workflows/workflows` + +This route returns a list of all workflows. + +#### GET `/review-workflows/workflows/:id` + +This route returns the details of a specific workflow identified by the id parameter. + +#### GET `/review-workflows/workflows/:workflow_id/stages` + +This route returns a list of all stages associated with a specific workflow identified by the workflow_id parameter. + +#### GET `/review-workflows/workflows/:workflow_id/stages/:id` + +This route returns the details of a specific stage identified by the id parameter and associated with the workflow identified by the workflow_id parameter. + +#### PUT `/review-workflows/workflows/:workflow_id/stages` + +This route updates the stages associated with a specific workflow identified by the workflow_id parameter. The updated stages are passed in the request body. + +#### PUT `/content-manager/(collection|single)-types/:model_uid/:id/stage` + +This route updates the stage of a specific entity identified by the id parameter and belonging to a specific collection identified by the model_uid parameter. The new stage value is passed in the request body. + +### Services + +The Review Workflow feature of the Enterprise Edition includes several services to manipulate workflows and stages. Here is a list of those services: + +#### review-workflows + +This service is used during the bootstrap and register phases of Strapi. Its primary responsibility is to migrate data on entities as needed and add the stage field to the entity schemas. + +#### workflows + +This service is used to manipulate the workflows entities. It provides functionalities to create, retrieve, and update workflows. + +#### stages + +This service is used to manipulate the stages entities and to update stages on other entities. It provides functionalities to create, retrieve, update, and delete stages. + +#### metrics + +This is the telemetry service used to gather information on the usage of this feature. It provides information on the number of workflows and stages created, as well as the frequency of stage updates on entities. + +### Decorators + +#### Entity Service + +The entity service is decorated so that entities can be linked to a default stage upon creation. This allows the entities to be automatically associated with a specific workflow stage when they are created. + +## Alternatives + +The Review Workflow feature is currently included as a core feature within the Strapi repository. However, there has been discussion about potentially moving it to a plugin in the future. While no decision has been made on this subject yet, it is possible that it may happen at some point in the future. + +## Resources + +- https://docs.strapi.io/user-docs/settings/review-workflows +- https://docs.strapi.io/user-docs/content-type-builder/creating-new-content-type#creating-a-new-content-type +- https://docs.strapi.io/user-docs/users-roles-permissions/configuring-administrator-roles#plugins-and-settings +- [Content manager](/content-manager/review-workflows) +- [Content type builder](/content-type-builder/review-workflows) diff --git a/docs/docs/docs/01-core/admin/01-ee/_category_.json b/docs/docs/docs/01-core/admin/01-ee/_category_.json new file mode 100644 index 0000000000..01af54de9d --- /dev/null +++ b/docs/docs/docs/01-core/admin/01-ee/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "EE", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/permissions/intro.mdx b/docs/docs/docs/01-core/admin/02-permissions/00-intro.mdx similarity index 100% rename from docs/docs/core/permissions/intro.mdx rename to docs/docs/docs/01-core/admin/02-permissions/00-intro.mdx diff --git a/docs/docs/core/permissions/how-they-work.mdx b/docs/docs/docs/01-core/admin/02-permissions/01-how-they-work.mdx similarity index 100% rename from docs/docs/core/permissions/how-they-work.mdx rename to docs/docs/docs/01-core/admin/02-permissions/01-how-they-work.mdx diff --git a/docs/docs/docs/01-core/admin/02-permissions/02-frontend/_category_.json b/docs/docs/docs/01-core/admin/02-permissions/02-frontend/_category_.json new file mode 100644 index 0000000000..050c32499c --- /dev/null +++ b/docs/docs/docs/01-core/admin/02-permissions/02-frontend/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Frontend", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/permissions/frontend/fetching-permissions.mdx b/docs/docs/docs/01-core/admin/02-permissions/02-frontend/fetching-permissions.mdx similarity index 100% rename from docs/docs/core/permissions/frontend/fetching-permissions.mdx rename to docs/docs/docs/01-core/admin/02-permissions/02-frontend/fetching-permissions.mdx diff --git a/docs/docs/core/permissions/frontend/using-permissions.mdx b/docs/docs/docs/01-core/admin/02-permissions/02-frontend/using-permissions.mdx similarity index 100% rename from docs/docs/core/permissions/frontend/using-permissions.mdx rename to docs/docs/docs/01-core/admin/02-permissions/02-frontend/using-permissions.mdx diff --git a/docs/docs/docs/01-core/admin/02-permissions/_category_.json b/docs/docs/docs/01-core/admin/02-permissions/_category_.json new file mode 100644 index 0000000000..87f9973cef --- /dev/null +++ b/docs/docs/docs/01-core/admin/02-permissions/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Permissions (RBAC)", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/settings/intro.mdx b/docs/docs/docs/01-core/admin/03-settings/00-intro.mdx similarity index 100% rename from docs/docs/core/settings/intro.mdx rename to docs/docs/docs/01-core/admin/03-settings/00-intro.mdx diff --git a/docs/docs/core/settings/review-workflows.mdx b/docs/docs/docs/01-core/admin/03-settings/01-review-workflows.mdx similarity index 100% rename from docs/docs/core/settings/review-workflows.mdx rename to docs/docs/docs/01-core/admin/03-settings/01-review-workflows.mdx diff --git a/docs/docs/docs/01-core/admin/03-settings/_category_.json b/docs/docs/docs/01-core/admin/03-settings/_category_.json new file mode 100644 index 0000000000..b1e0a4c2ed --- /dev/null +++ b/docs/docs/docs/01-core/admin/03-settings/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Settings", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/docs/01-core/admin/_category_.json b/docs/docs/docs/01-core/admin/_category_.json new file mode 100644 index 0000000000..5ef3c188d9 --- /dev/null +++ b/docs/docs/docs/01-core/admin/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Admin", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/content-manager/intro.md b/docs/docs/docs/01-core/content-manager/00-intro.md similarity index 93% rename from docs/docs/core/content-manager/intro.md rename to docs/docs/docs/01-core/content-manager/00-intro.md index 9b9b44bbf3..6c7aa22978 100644 --- a/docs/docs/core/content-manager/intro.md +++ b/docs/docs/docs/01-core/content-manager/00-intro.md @@ -1,6 +1,5 @@ --- title: Introduction -slug: /content-manager tags: - content-manager --- diff --git a/docs/docs/core/content-manager/relations.mdx b/docs/docs/docs/01-core/content-manager/01-relations.mdx similarity index 99% rename from docs/docs/core/content-manager/relations.mdx rename to docs/docs/docs/01-core/content-manager/01-relations.mdx index ec48b399ef..2034024eaf 100644 --- a/docs/docs/core/content-manager/relations.mdx +++ b/docs/docs/docs/01-core/content-manager/01-relations.mdx @@ -1,6 +1,5 @@ --- title: Relations -slug: /content-manager/relations description: Conceptual guide to relations in the Content Manager focussing on the technical decisions taken. tags: - content-manager diff --git a/docs/docs/core/content-manager/review-workflows.mdx b/docs/docs/docs/01-core/content-manager/02-review-workflows.mdx similarity index 98% rename from docs/docs/core/content-manager/review-workflows.mdx rename to docs/docs/docs/01-core/content-manager/02-review-workflows.mdx index f762cac565..679f7692a8 100644 --- a/docs/docs/core/content-manager/review-workflows.mdx +++ b/docs/docs/docs/01-core/content-manager/02-review-workflows.mdx @@ -1,6 +1,5 @@ --- title: Review Workflows -slug: /content-manager/review-workflows description: Guide for review workflows in the content-manager. tags: - content-manager @@ -90,6 +89,6 @@ Assigns a stage to an entity. ```ts data: { - id: int // assigned stage id + id: int; // assigned stage id } ``` diff --git a/docs/docs/docs/01-core/content-manager/_category_.json b/docs/docs/docs/01-core/content-manager/_category_.json new file mode 100644 index 0000000000..ad9837d3b8 --- /dev/null +++ b/docs/docs/docs/01-core/content-manager/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Content Manager", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/docs/01-core/content-manager/hooks/_category_.json b/docs/docs/docs/01-core/content-manager/hooks/_category_.json new file mode 100644 index 0000000000..e1f16ed4b4 --- /dev/null +++ b/docs/docs/docs/01-core/content-manager/hooks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Hooks", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/docs/01-core/content-manager/hooks/use-content-types.mdx b/docs/docs/docs/01-core/content-manager/hooks/use-content-types.mdx new file mode 100644 index 0000000000..ec0f64a28d --- /dev/null +++ b/docs/docs/docs/01-core/content-manager/hooks/use-content-types.mdx @@ -0,0 +1,25 @@ +--- +title: useContentTypes +description: API reference for the useContentTypes hook in Strapi's Content Manager +tags: + - content-manager + - hooks + - fetch + - content-types + - components +--- + +An abstraction around `react-query` to fetch content-types and components. It returns the raw API response +for components. `collectionTypes` and `singleTypes` are filtered by `isDisplayed=true`. + +## Usage + +```jsx +import { useContentTypes } from 'path/to/hooks'; + +const MyComponent = () => { + const { isLoading, collectionTypes, singleTypes, components } = useContentTypes(); + + return (/* ... */); +}; +``` diff --git a/docs/docs/core/content-manager/hooks/use-drag-and-drop.mdx b/docs/docs/docs/01-core/content-manager/hooks/use-drag-and-drop.mdx similarity index 99% rename from docs/docs/core/content-manager/hooks/use-drag-and-drop.mdx rename to docs/docs/docs/01-core/content-manager/hooks/use-drag-and-drop.mdx index a38ae7b7ec..f7e01a4229 100644 --- a/docs/docs/core/content-manager/hooks/use-drag-and-drop.mdx +++ b/docs/docs/docs/01-core/content-manager/hooks/use-drag-and-drop.mdx @@ -1,6 +1,5 @@ --- title: useDragAndDrop -slug: /content-manager/hooks/use-drag-and-drop description: API reference for the useDragAndDrop hook in Strapi's Content Manager tags: - content-manager diff --git a/docs/docs/core/content-type-builder/intro.md b/docs/docs/docs/01-core/content-type-builder/00-intro.md similarity index 100% rename from docs/docs/core/content-type-builder/intro.md rename to docs/docs/docs/01-core/content-type-builder/00-intro.md diff --git a/docs/docs/core/content-type-builder/review-workflows.mdx b/docs/docs/docs/01-core/content-type-builder/01-review-workflows.mdx similarity index 88% rename from docs/docs/core/content-type-builder/review-workflows.mdx rename to docs/docs/docs/01-core/content-type-builder/01-review-workflows.mdx index 22f6547657..176433f2b2 100644 --- a/docs/docs/core/content-type-builder/review-workflows.mdx +++ b/docs/docs/docs/01-core/content-type-builder/01-review-workflows.mdx @@ -1,6 +1,5 @@ --- title: Review Workflows -slug: /content-type-builder/review-workflows description: Guide for review workflows in the content-type-builder. tags: - content-type-builder @@ -16,8 +15,8 @@ modal. Similar to draft & publish review-workflows registers a new input component type called `toggle-review-workflows` which is used to render the checkbox component. -**Note**: *Ideally the code should have been placed in the `ee` folder to be -under the enterprise license, but neither the content-type-builder nor the babel-plugin to transpile the ee code had support for this.* +**Note**: _Ideally the code should have been placed in the `ee` folder to be +under the enterprise license, but neither the content-type-builder nor the babel-plugin to transpile the ee code had support for this._ ## Endpoints diff --git a/docs/docs/docs/01-core/content-type-builder/_category_.json b/docs/docs/docs/01-core/content-type-builder/_category_.json new file mode 100644 index 0000000000..38c62f92cc --- /dev/null +++ b/docs/docs/docs/01-core/content-type-builder/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Content Type Builder", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/database/intro.md b/docs/docs/docs/01-core/database/00-intro.md similarity index 100% rename from docs/docs/core/database/intro.md rename to docs/docs/docs/01-core/database/00-intro.md diff --git a/docs/docs/docs/01-core/database/_category_.json b/docs/docs/docs/01-core/database/_category_.json new file mode 100644 index 0000000000..0b1c7a87da --- /dev/null +++ b/docs/docs/docs/01-core/database/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Database", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/docs/01-core/database/relations/_category_.json b/docs/docs/docs/01-core/database/relations/_category_.json new file mode 100644 index 0000000000..b46947c8a4 --- /dev/null +++ b/docs/docs/docs/01-core/database/relations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Relations", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/database/relations/reordering.mdx b/docs/docs/docs/01-core/database/relations/reordering.mdx similarity index 98% rename from docs/docs/core/database/relations/reordering.mdx rename to docs/docs/docs/01-core/database/relations/reordering.mdx index e136088482..8891851a21 100644 --- a/docs/docs/core/database/relations/reordering.mdx +++ b/docs/docs/docs/01-core/database/relations/reordering.mdx @@ -1,6 +1,5 @@ --- -title: Relations -slug: /database/relations/reordering +title: Reordering description: Conceptual guide to relations reordering in the Database tags: - database diff --git a/docs/docs/docs/01-core/helper-plugin/_category_.json b/docs/docs/docs/01-core/helper-plugin/_category_.json new file mode 100644 index 0000000000..0f1d4dd023 --- /dev/null +++ b/docs/docs/docs/01-core/helper-plugin/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Helper Plugin", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/docs/01-core/helper-plugin/hooks/_category_.json b/docs/docs/docs/01-core/helper-plugin/hooks/_category_.json new file mode 100644 index 0000000000..e1f16ed4b4 --- /dev/null +++ b/docs/docs/docs/01-core/helper-plugin/hooks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Hooks", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/helper-plugin/hooks/use-api-error-handler.mdx b/docs/docs/docs/01-core/helper-plugin/hooks/use-api-error-handler.mdx similarity index 100% rename from docs/docs/core/helper-plugin/hooks/use-api-error-handler.mdx rename to docs/docs/docs/01-core/helper-plugin/hooks/use-api-error-handler.mdx diff --git a/docs/docs/core/helper-plugin/hooks/use-callback-ref.mdx b/docs/docs/docs/01-core/helper-plugin/hooks/use-callback-ref.mdx similarity index 94% rename from docs/docs/core/helper-plugin/hooks/use-callback-ref.mdx rename to docs/docs/docs/01-core/helper-plugin/hooks/use-callback-ref.mdx index a468cbe06d..9caa02681d 100644 --- a/docs/docs/core/helper-plugin/hooks/use-callback-ref.mdx +++ b/docs/docs/docs/01-core/helper-plugin/hooks/use-callback-ref.mdx @@ -17,7 +17,7 @@ Borrowed from [`@radix-ui/react-use-callback-ref`](https://www.npmjs.com/package ## Usage ```jsx -import { useCallbackRef } from 'path/to/hooks'; +import { useCallbackRef } from '@strapi/helper-plugin'; const MyComponent = ({ callbackFromSomewhere }) => { const mySafeCallback = useCallbackRef(callbackFromSomewhere); diff --git a/docs/docs/docs/01-core/helper-plugin/hooks/use-clipboard.mdx b/docs/docs/docs/01-core/helper-plugin/hooks/use-clipboard.mdx new file mode 100644 index 0000000000..e873b84b9d --- /dev/null +++ b/docs/docs/docs/01-core/helper-plugin/hooks/use-clipboard.mdx @@ -0,0 +1,37 @@ +--- +title: useClipboard +description: API reference for the useClipboard hook in Strapi +tags: + - hooks + - helper-plugin +--- + +A small abstraction around the [`navigation.clipboard`](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard) API. +Currently we only expose a `copy` method which abstracts the `writeText` method of the clipboard API. + +## Usage + +```jsx +import { useClipboard } from '@strapi/helper-plugin'; + +const MyComponent = () => { + const { copy } = useClipboard(); + const handleClick = async () => { + const didCopy = await copy('hello world'); + + if (didCopy) { + alert('copied!'); + } + }; + + return ; +}; +``` + +## Typescript + +```ts +function useClipboard(): { + copy: (text: string) => Promise; +}; +``` diff --git a/docs/docs/core/helper-plugin/hooks/use-collator.mdx b/docs/docs/docs/01-core/helper-plugin/hooks/use-collator.mdx similarity index 100% rename from docs/docs/core/helper-plugin/hooks/use-collator.mdx rename to docs/docs/docs/01-core/helper-plugin/hooks/use-collator.mdx diff --git a/docs/docs/core/helper-plugin/hooks/use-fetch-client.mdx b/docs/docs/docs/01-core/helper-plugin/hooks/use-fetch-client.mdx similarity index 95% rename from docs/docs/core/helper-plugin/hooks/use-fetch-client.mdx rename to docs/docs/docs/01-core/helper-plugin/hooks/use-fetch-client.mdx index 747ad7d324..5debb4794b 100644 --- a/docs/docs/core/helper-plugin/hooks/use-fetch-client.mdx +++ b/docs/docs/docs/01-core/helper-plugin/hooks/use-fetch-client.mdx @@ -40,7 +40,7 @@ const Component = () => { ``` :::tip -Remember to use a relative path for your requestURL, following this format `/{yourRelativePath}` +The expected URL style includes either a protocol (such as HTTP or HTTPS) or a relative URL. The URLs with domain and path but not protocol are not allowed (ex: `www.example.com`). ::: ## Methods diff --git a/docs/docs/core/helper-plugin/hooks/use-filter.mdx b/docs/docs/docs/01-core/helper-plugin/hooks/use-filter.mdx similarity index 100% rename from docs/docs/core/helper-plugin/hooks/use-filter.mdx rename to docs/docs/docs/01-core/helper-plugin/hooks/use-filter.mdx diff --git a/docs/docs/docs/01-core/strapi/_category_.json b/docs/docs/docs/01-core/strapi/_category_.json new file mode 100644 index 0000000000..0adfa37278 --- /dev/null +++ b/docs/docs/docs/01-core/strapi/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Strapi", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/utils/event-hub.md b/docs/docs/docs/01-core/strapi/event-hub.md similarity index 100% rename from docs/docs/core/utils/event-hub.md rename to docs/docs/docs/01-core/strapi/event-hub.md diff --git a/docs/docs/core/upload/intro.md b/docs/docs/docs/01-core/upload/00-intro.md similarity index 100% rename from docs/docs/core/upload/intro.md rename to docs/docs/docs/01-core/upload/00-intro.md diff --git a/docs/docs/core/upload/backend/providers/intro.md b/docs/docs/docs/01-core/upload/01-backend/00-providers.md similarity index 98% rename from docs/docs/core/upload/backend/providers/intro.md rename to docs/docs/docs/01-core/upload/01-backend/00-providers.md index 96a3f50846..6751dab401 100644 --- a/docs/docs/core/upload/backend/providers/intro.md +++ b/docs/docs/docs/01-core/upload/01-backend/00-providers.md @@ -1,5 +1,5 @@ --- -title: Introduction +title: Providers slug: /upload tags: - upload diff --git a/docs/docs/docs/01-core/upload/01-backend/_category_.json b/docs/docs/docs/01-core/upload/01-backend/_category_.json new file mode 100644 index 0000000000..76c281f9f2 --- /dev/null +++ b/docs/docs/docs/01-core/upload/01-backend/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Backend", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/docs/01-core/upload/_category_.json b/docs/docs/docs/01-core/upload/_category_.json new file mode 100644 index 0000000000..99f59aa94b --- /dev/null +++ b/docs/docs/docs/01-core/upload/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upload", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/docs/01-core/utils/_category_.json b/docs/docs/docs/01-core/utils/_category_.json new file mode 100644 index 0000000000..3bac2fda1f --- /dev/null +++ b/docs/docs/docs/01-core/utils/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Utils", + "collapsible": true, + "collapsed": true +} diff --git a/docs/docs/core/utils/async.md b/docs/docs/docs/01-core/utils/async.md similarity index 100% rename from docs/docs/core/utils/async.md rename to docs/docs/docs/01-core/utils/async.md diff --git a/docs/docs/community/0-contributing.md b/docs/docs/guides/00-contributing.md similarity index 85% rename from docs/docs/community/0-contributing.md rename to docs/docs/guides/00-contributing.md index f9919207e9..606d20280e 100644 --- a/docs/docs/community/0-contributing.md +++ b/docs/docs/guides/00-contributing.md @@ -1,5 +1,5 @@ --- -title: Contributing +title: Contributing to Strapi hide_title: true --- diff --git a/docs/docs/community/1-code-of-conduct.md b/docs/docs/guides/01-code-of-conduct.md similarity index 100% rename from docs/docs/community/1-code-of-conduct.md rename to docs/docs/guides/01-code-of-conduct.md diff --git a/docs/docs/core/admin/link-strapi-design-system.md b/docs/docs/guides/02-working-with-the-design-system.md similarity index 88% rename from docs/docs/core/admin/link-strapi-design-system.md rename to docs/docs/guides/02-working-with-the-design-system.md index 0d9e0bed93..7db0924d33 100644 --- a/docs/docs/core/admin/link-strapi-design-system.md +++ b/docs/docs/guides/02-working-with-the-design-system.md @@ -1,4 +1,8 @@ -# Linking the Strapi Design System +--- +title: Working with the Design System +--- + +## Linking the Strapi Design System Follow these steps to use a local version of the Strapi design system with the Strapi monorepo diff --git a/docs/docs/guides/03-typescript.md b/docs/docs/guides/03-typescript.md new file mode 100644 index 0000000000..af29bb293d --- /dev/null +++ b/docs/docs/guides/03-typescript.md @@ -0,0 +1,5 @@ +--- +title: Typescript +--- + +🚧 coming soon 🚧 diff --git a/docs/docs/index.md b/docs/docs/index.md index 02b08a03c9..011c535887 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -4,3 +4,23 @@ sidebar_label: Introduction --- # Strapi contributor documentation + +Welcome to the Strapi Contributor documentation. + +This documentation site is a constant WIP so please, continue to add and improve these docs. The general layout focusses on 4 key areas: + +## Guides + +This is where you'll probably want to start off. We have our contributing guides & code of conduct which are very important to read. There are also useful guides on common situations whilst developing such as ["Working with the Design System"](/guides/working-with-the-design-system) and higher-level guides such as best practices for frontend development (coming soon). + +## Docs + +Within the docs section we have a multitude of both technical and conceptual documentation diving deep into particular parts of the Strapi monorepo that may not make as much sense as just reading the code, like ["Relations reordering in the database"](/docs/core/database/relations/reordering). There's also usage documentation for various pieces of code such as the [useDragAndDrop](/docs/core/content-manager/hooks/use-drag-and-drop) hook. + +## API Reference + +An advanced deep dive into some of the core driving classes of Strapi with explanations on the methods & parameters available on commonly exposed classes as well as examples to compliment them for easier understanding. + +## RFCs + +A growing section we intend to populate over time with public-facing RFCs once approved to maintain as a record. These assist in understanding the design direction of features and code to understand the contextual "whys" that may not be apparent. diff --git a/docs/docs/rfcs/00-intro.md b/docs/docs/rfcs/00-intro.md new file mode 100644 index 0000000000..242306c709 --- /dev/null +++ b/docs/docs/rfcs/00-intro.md @@ -0,0 +1,5 @@ +--- +title: Introduction +--- + +This section of the contributor docs is a collection of public facing RFCs. diff --git a/docs/docs/custom-fields.md b/docs/docs/rfcs/01-custom-fields.md similarity index 99% rename from docs/docs/custom-fields.md rename to docs/docs/rfcs/01-custom-fields.md index 49ebe3933e..54528b3cc8 100644 --- a/docs/docs/custom-fields.md +++ b/docs/docs/rfcs/01-custom-fields.md @@ -1,6 +1,5 @@ --- title: Custom fields -slug: /custom-fields tags: - content-type-builder - plugins diff --git a/docs/docs/example.md b/docs/docs/rfcs/example.md similarity index 79% rename from docs/docs/example.md rename to docs/docs/rfcs/example.md index 6325f58a6b..7b9265585d 100644 --- a/docs/docs/example.md +++ b/docs/docs/rfcs/example.md @@ -1,11 +1,15 @@ --- -title: Example +title: RFC Example Doc description: Short description tags: - content-manager --- -# Example doc +# RFC Example Doc + +Interested in submitting your own public RFC? Use this template as your basis but feel free to expand on it should your needs require it. + +--- ## Summary diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index bf43d45cd4..c31f39d513 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -67,22 +67,28 @@ const config = { }, items: [ { - type: 'doc', + type: 'docSidebar', position: 'left', - docId: 'index', + sidebarId: 'guides', + label: 'Guides', + }, + { + type: 'docSidebar', + position: 'left', + sidebarId: 'docs', label: 'Docs', }, { type: 'docSidebar', position: 'left', sidebarId: 'api', - label: 'API', + label: 'API Reference', }, { type: 'docSidebar', position: 'left', - sidebarId: 'community', - label: 'Community', + sidebarId: 'rfcs', + label: 'RFCs', }, ], }, diff --git a/docs/sidebars.js b/docs/sidebars.js index 0c85976892..18a7b22ac8 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -14,218 +14,10 @@ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure - docs: [ - 'index', - { - type: 'category', - label: 'Admin', - items: [ - { - type: 'doc', - label: 'Link Strapi Design System', - id: 'core/admin/link-strapi-design-system', - }, - ], - }, - { - type: 'category', - label: 'Core', - link: { - type: 'generated-index', - }, - collapsible: false, - items: [ - { - type: 'category', - label: 'Admin', - items: [ - { - type: 'doc', - label: 'Link Strapi Design System', - id: 'core/admin/link-strapi-design-system', - }, - ], - }, - { - type: 'category', - label: 'Content Manager', - link: { - type: 'doc', - id: 'core/content-manager/intro', - }, - items: [ - { - type: 'category', - label: 'Hooks', - items: [ - { - type: 'doc', - label: 'useDragAndDrop', - id: 'core/content-manager/hooks/use-drag-and-drop', - }, - ], - }, - { - type: 'doc', - label: 'Relations', - id: 'core/content-manager/relations', - }, - { - type: 'doc', - label: 'Review Workflows', - id: 'core/content-manager/review-workflows', - }, - ], - }, - { - type: 'category', - label: 'Content Type Builder', - link: { - type: 'doc', - id: 'core/content-type-builder/intro', - }, - items: [ - { - type: 'doc', - label: 'Review Workflows', - id: 'core/content-type-builder/review-workflows', - }, - ], - }, - { - type: 'category', - label: 'Database', - link: { - type: 'doc', - id: 'core/database/intro', - }, - items: [ - { - type: 'category', - label: 'Relations', - items: [ - { - type: 'doc', - label: 'Reordering', - id: 'core/database/relations/reordering', - }, - ], - }, - ], - }, - { - type: 'category', - label: 'Helper Plugin', - items: [ - { - type: 'category', - label: 'Hooks', - items: [ - { - type: 'doc', - label: 'useAPIErrorHandler', - id: 'core/helper-plugin/hooks/use-api-error-handler', - }, - { - type: 'doc', - label: 'useCallbackRef', - id: 'core/helper-plugin/hooks/use-callback-ref', - }, - { - type: 'doc', - label: 'useCollator', - id: 'core/helper-plugin/hooks/use-collator', - }, - { - type: 'doc', - label: 'useFetchClient', - id: 'core/helper-plugin/hooks/use-fetch-client', - }, - { - type: 'doc', - label: 'useFilter', - id: 'core/helper-plugin/hooks/use-filter', - }, - ], - }, - ], - }, - { - type: 'category', - label: 'Permissions (RBAC)', - link: { - type: 'doc', - id: 'core/permissions/intro', - }, - items: [ - { - type: 'doc', - label: 'How Permissions Work', - id: 'core/permissions/how-they-work', - }, - { - type: 'category', - label: 'RBAC on the frontend', - items: [ - { - type: 'doc', - label: 'Fetching Permissions', - id: 'core/permissions/frontend/fetching-permissions', - }, - { - type: 'doc', - label: 'Using Permissions', - id: 'core/permissions/frontend/using-permissions', - }, - ], - }, - ], - }, - { - type: 'category', - label: 'Settings', - link: { - type: 'doc', - id: 'core/settings/intro', - }, - items: [ - { - type: 'doc', - label: 'Review Workflows', - id: 'core/settings/review-workflows', - }, - ], - }, - { - type: 'category', - label: 'Utils', - items: [ - { - type: 'doc', - label: 'Async', - id: 'core/utils/async', - }, - { - type: 'doc', - label: 'Event Hub', - id: 'core/utils/event-hub', - }, - ], - }, - ], - }, - { - type: 'category', - label: 'Custom Fields', - link: { - type: 'doc', - id: 'custom-fields', - }, - items: [], - }, - ], + docs: [{ type: 'autogenerated', dirName: 'docs' }], api: [{ type: 'autogenerated', dirName: 'api' }], - community: [{ type: 'autogenerated', dirName: 'community' }], + guides: [{ type: 'autogenerated', dirName: 'guides' }], + rfcs: [{ type: 'autogenerated', dirName: 'rfcs' }], }; module.exports = sidebars; diff --git a/examples/getstarted/package.json b/examples/getstarted/package.json index a831dbec79..79081fb371 100644 --- a/examples/getstarted/package.json +++ b/examples/getstarted/package.json @@ -1,7 +1,7 @@ { "name": "getstarted", "private": true, - "version": "4.10.1", + "version": "4.10.5", "description": "A Strapi application.", "scripts": { "develop": "strapi develop", @@ -12,26 +12,25 @@ "strapi": "strapi" }, "dependencies": { - "@strapi/icons": "1.6.6", - "@strapi/plugin-color-picker": "4.10.1", - "@strapi/plugin-documentation": "4.10.1", - "@strapi/plugin-graphql": "4.10.1", - "@strapi/plugin-i18n": "4.10.1", - "@strapi/plugin-sentry": "4.10.1", - "@strapi/plugin-users-permissions": "4.10.1", - "@strapi/provider-email-mailgun": "4.10.1", - "@strapi/provider-upload-aws-s3": "4.10.1", - "@strapi/provider-upload-cloudinary": "4.10.1", - "@strapi/strapi": "4.10.1", - "@vscode/sqlite3": "5.1.2", - "better-sqlite3": "8.0.1", + "@strapi/icons": "1.7.7", + "@strapi/plugin-color-picker": "4.10.5", + "@strapi/plugin-documentation": "4.10.5", + "@strapi/plugin-graphql": "4.10.5", + "@strapi/plugin-i18n": "4.10.5", + "@strapi/plugin-sentry": "4.10.5", + "@strapi/plugin-users-permissions": "4.10.5", + "@strapi/provider-email-mailgun": "4.10.5", + "@strapi/provider-upload-aws-s3": "4.10.5", + "@strapi/provider-upload-cloudinary": "4.10.5", + "@strapi/strapi": "4.10.5", + "better-sqlite3": "8.3.0", "lodash": "4.17.21", "mysql": "2.18.1", - "mysql2": "3.2.0", + "mysql2": "3.3.0", "passport-google-oauth2": "0.2.0", "pg": "8.8.0", "react": "^17.0.2", - "react-intl": "6.3.2", + "react-intl": "6.4.1", "sqlite3": "5.1.2" }, "strapi": { diff --git a/examples/kitchensink-ts/package.json b/examples/kitchensink-ts/package.json index 4858267f60..bcb34091a6 100644 --- a/examples/kitchensink-ts/package.json +++ b/examples/kitchensink-ts/package.json @@ -1,7 +1,7 @@ { "name": "kitchensink-ts", "private": true, - "version": "4.10.1", + "version": "4.10.5", "description": "A Strapi application", "scripts": { "develop": "strapi develop", @@ -10,10 +10,10 @@ "strapi": "strapi" }, "dependencies": { - "@strapi/plugin-i18n": "4.10.1", - "@strapi/plugin-users-permissions": "4.10.1", - "@strapi/strapi": "4.10.1", - "better-sqlite3": "8.0.1" + "@strapi/plugin-i18n": "4.10.5", + "@strapi/plugin-users-permissions": "4.10.5", + "@strapi/strapi": "4.10.5", + "better-sqlite3": "8.3.0" }, "author": { "name": "A Strapi developer" diff --git a/examples/kitchensink/package.json b/examples/kitchensink/package.json index 14ff7c338c..cfe0fc89a8 100644 --- a/examples/kitchensink/package.json +++ b/examples/kitchensink/package.json @@ -1,7 +1,7 @@ { "name": "kitchensink", "private": true, - "version": "4.10.1", + "version": "4.10.5", "description": "A Strapi application.", "scripts": { "develop": "strapi develop", @@ -12,10 +12,10 @@ "strapi": "strapi" }, "dependencies": { - "@strapi/provider-email-mailgun": "4.10.1", - "@strapi/provider-upload-aws-s3": "4.10.1", - "@strapi/provider-upload-cloudinary": "4.10.1", - "@strapi/strapi": "4.10.1", + "@strapi/provider-email-mailgun": "4.10.5", + "@strapi/provider-upload-aws-s3": "4.10.5", + "@strapi/provider-upload-cloudinary": "4.10.5", + "@strapi/strapi": "4.10.5", "lodash": "4.17.21", "mysql": "2.18.1", "passport-google-oauth2": "0.2.0", diff --git a/jest-preset.front.js b/jest-preset.front.js index 37dac4fc8d..85f4dcdb09 100644 --- a/jest-preset.front.js +++ b/jest-preset.front.js @@ -30,7 +30,9 @@ const moduleNameMapper = { module.exports = { rootDir: __dirname, moduleNameMapper, - testPathIgnorePatterns: ['/node_modules/', '__tests__'], + /* Tells jest to ignore duplicated manual mock files, such as index.js */ + modulePathIgnorePatterns: ['.*__mocks__.*'], + testPathIgnorePatterns: ['node_modules/', '__tests__'], globalSetup: '@strapi/admin-test-utils/global-setup', setupFiles: ['@strapi/admin-test-utils/environment'], setupFilesAfterEnv: ['@strapi/admin-test-utils/after-env'], @@ -60,7 +62,7 @@ module.exports = { transformIgnorePatterns: [ 'node_modules/(?!(react-dnd|dnd-core|react-dnd-html5-backend|@strapi/design-system|@strapi/icons|fractional-indexing)/)', ], - testMatch: ['/**/tests/**/?(*.)+(spec|test).[jt]s?(x)'], + testMatch: ['**/tests/**/?(*.)+(spec|test).[jt]s?(x)'], testEnvironmentOptions: { url: 'http://localhost:1337/admin', }, diff --git a/jest-preset.unit.js b/jest-preset.unit.js index 18da25e7f1..20f4300ce0 100644 --- a/jest-preset.unit.js +++ b/jest-preset.unit.js @@ -4,7 +4,10 @@ module.exports = { setupFilesAfterEnv: [__dirname + '/test/unit.setup.js'], modulePathIgnorePatterns: ['.cache', 'dist'], testPathIgnorePatterns: ['.testdata.js', '.test.utils.js'], - testMatch: ['/**/__tests__/**/*.[jt]s?(x)'], + testMatch: ['**/__tests__/**/*.{js,ts,jsx,tsx}'], + transform: { + '^.+\\.(t|j)sx?$': ['@swc/jest'], + }, // Use `jest-watch-typeahead` version 0.6.5. Newest version 1.0.0 does not support jest@26 // Reference: https://github.com/jest-community/jest-watch-typeahead/releases/tag/v1.0.0 watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'], diff --git a/jest.config.front.js b/jest.config.front.js new file mode 100644 index 0000000000..be0d1bce5f --- /dev/null +++ b/jest.config.front.js @@ -0,0 +1,12 @@ +'use strict'; + +/** @type {import('jest').Config} */ +const config = { + projects: [ + '/packages/plugins/*/jest.config.front.js', + '/packages/core/*/jest.config.front.js', + '/scripts/*/jest.config.front.js', + ], +}; + +module.exports = config; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000000..6ef1408f68 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,15 @@ +'use strict'; + +/** @type {import('jest').Config} */ +const config = { + projects: [ + '/packages/plugins/*/jest.config.js', + '/packages/utils/*/jest.config.js', + '/packages/generators/*/jest.config.js', + '/packages/core/*/jest.config.js', + '/packages/providers/*/jest.config.js', + '/.github/actions/*/jest.config.js', + ], +}; + +module.exports = config; diff --git a/lerna.json b/lerna.json index 4efa55cc09..6c7c8ff7bd 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "4.10.1", + "version": "4.10.5", "packages": ["packages/*", "examples/*"], "npmClient": "yarn", "useWorkspaces": true, diff --git a/lint-staged.config.js b/lint-staged.config.js new file mode 100644 index 0000000000..4469ce4c24 --- /dev/null +++ b/lint-staged.config.js @@ -0,0 +1,50 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const findUp = require('find-up'); + +const includes = ['packages', '.github']; + +const root = path.resolve(__dirname); + +function extractPackageName(pkgJsonPath) { + return JSON.parse(fs.readFileSync(pkgJsonPath).toString()).name; +} + +function getLintCommand(files) { + const affectedFolders = new Set(); + + for (const file of files) { + const r = findUp.sync('package.json', { cwd: file }); + const relPath = path.relative(root, r); + + if (includes.some((incl) => relPath.startsWith(incl))) { + affectedFolders.add(r); + } + } + + const affectedPackages = [...affectedFolders].map(extractPackageName); + + if (affectedPackages.length === 0) { + return null; + } + return `nx run-many -t lint -p ${affectedPackages.join()}`; +} + +function getCodeCommands(files) { + const lintCmd = getLintCommand(files); + + const prettierCmd = `prettier --write ${files.join(' ')}`; + + if (lintCmd) { + return [lintCmd, prettierCmd]; + } + + return [prettierCmd]; +} + +module.exports = { + '*.{js,ts}': getCodeCommands, + '*.{md,css,scss,yaml,yml}': ['prettier --write'], +}; diff --git a/nx.json b/nx.json index 9e217a281e..d100fd87c7 100644 --- a/nx.json +++ b/nx.json @@ -29,12 +29,10 @@ "dependsOn": ["^build:ts"] }, "test:unit": { - "inputs": ["default", "{workspaceRoot}/jest-preset.unit.js"], - "dependsOn": ["build:ts"] + "inputs": ["default", "{workspaceRoot}/jest-preset.unit.js"] }, "test:front": { - "inputs": ["default", "{workspaceRoot}/jest-preset.front.js"], - "dependsOn": ["^build"] + "inputs": ["default", "{workspaceRoot}/jest-preset.front.js"] }, "lint": { "inputs": [ @@ -44,8 +42,7 @@ "{projectRoot}/.eslintignore", "{projectRoot}/tsconfig.eslint.json", "{workspaceRoot}/packages/utils/eslint-config-custom/**/*" - ], - "dependsOn": ["build:ts"] + ] } }, "tasksRunnerOptions": { diff --git a/package.json b/package.json index da73e2265e..4330307e53 100644 --- a/package.json +++ b/package.json @@ -45,23 +45,21 @@ "format:other": "yarn prettier:other --write", "prettier:code": "prettier --cache --cache-strategy content \"**/*.{js,ts}\"", "prettier:other": "prettier --cache --cache-strategy content \"**/*.{md,css,scss,yaml,yml}\"", - "test:front": "cross-env IS_EE=true nx run-many --target=test:front --nx-ignore-cycles", - "test:front:watch": "cross-env IS_EE=true nx run-many --target=test:front:watch --nx-ignore-cycles", - "test:front:update": "yarn test:front -u", - "test:front:ce": "cross-env IS_EE=false nx run-many --target=test:front --nx-ignore-cycles", - "test:front:watch:ce": "cross-env IS_EE=false nx run-many --target=test:front:watch --nx-ignore-cycles", + "test:front:all": "cross-env IS_EE=true nx run-many --target=test:front --nx-ignore-cycles", + "test:front": "cross-env IS_EE=true jest --config jest.config.front.js", + "test:front:watch": "cross-env IS_EE=true run test:front --watch", + "test:front:update": "run test:front -u", + "test:front:all:ce": "cross-env IS_EE=false nx run-many --target=test:front:ce --nx-ignore-cycles", + "test:front:ce": "cross-env IS_EE=false run test:front", + "test:front:watch:ce": "cross-env IS_EE=false run test:front --watch", "test:front:update:ce": "yarn test:front:ce -u", - "test:unit": "nx run-many --target=test:unit --nx-ignore-cycles", - "test:unit:watch": "nx run-many --target=test:unit:watch --nx-ignore-cycles", + "test:unit:all": "nx run-many --target=test:unit --nx-ignore-cycles", + "test:unit": "jest --config jest.config.js", + "test:unit:watch": "run test:unit --watch", "test:api": "node test/api.js", "test:generate-app": "node test/create-test-app.js", "doc:api": "node scripts/open-api/serve.js" }, - "lint-staged": { - "*.{js,ts,md,css,scss,yaml,yml}": [ - "prettier --write" - ] - }, "devDependencies": { "@babel/core": "^7.20.12", "@babel/eslint-parser": "^7.19.1", @@ -69,17 +67,18 @@ "@strapi/admin-test-utils": "workspace:*", "@strapi/eslint-config": "0.1.2", "@swc/cli": "0.1.62", - "@swc/core": "1.3.37", - "@swc/jest": "0.2.24", - "@typescript-eslint/eslint-plugin": "^5.55.0", - "@typescript-eslint/parser": "5.43.0", + "@swc/core": "1.3.58", + "@swc/helpers": "0.5.1", + "@swc/jest": "0.2.26", + "@typescript-eslint/eslint-plugin": "5.59.1", + "@typescript-eslint/parser": "5.59.1", "babel-eslint": "10.1.0", "chalk": "4.1.2", "chokidar": "3.5.3", - "core-js": "3.28.0", + "core-js": "3.30.1", "cross-env": "7.0.3", "dotenv": "14.2.0", - "eslint": "8.27.0", + "eslint": "8.41.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-airbnb-typescript": "^17.0.0", @@ -91,6 +90,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "execa": "1.0.0", + "find-up": "5.0.0", "fs-extra": "10.1.0", "get-port": "5.1.1", "glob": "7.2.3", @@ -98,13 +98,13 @@ "inquirer": "8.2.5", "jest": "29.0.3", "jest-circus": "29.0.3", - "jest-cli": "29.0.3", + "jest-cli": "29.5.0", "jest-environment-jsdom": "29.0.3", "jest-watch-typeahead": "2.2.2", "lerna": "6.5.1", - "lint-staged": "13.0.3", + "lint-staged": "13.2.2", "lodash": "4.17.21", - "nx": "15.8.3", + "nx": "15.9.4", "plop": "2.7.6", "prettier": "2.8.4", "qs": "6.11.1", diff --git a/packages/admin-test-utils/custom.d.ts b/packages/admin-test-utils/custom.d.ts index ac37bd91b2..19947d3565 100644 --- a/packages/admin-test-utils/custom.d.ts +++ b/packages/admin-test-utils/custom.d.ts @@ -10,6 +10,7 @@ declare global { isEnabled: (featureName?: string) => boolean; }; projectType: string; + telemetryDisabled: boolean; }; } } diff --git a/packages/admin-test-utils/package.json b/packages/admin-test-utils/package.json index 586af47ba8..0f5a30088c 100644 --- a/packages/admin-test-utils/package.json +++ b/packages/admin-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/admin-test-utils", - "version": "4.10.1", + "version": "4.10.5", "private": true, "description": "Test utilities for the Strapi administration panel", "license": "MIT", @@ -38,12 +38,13 @@ "dependencies": { "@juggle/resize-observer": "3.4.0", "@testing-library/jest-dom": "5.16.5", - "jest-styled-components": "7.1.1" + "jest-styled-components": "7.1.1", + "whatwg-fetch": "3.6.2" }, "devDependencies": { - "eslint-config-custom": "4.10.1", + "eslint-config-custom": "4.10.5", "redux": "^4.2.1", - "tsconfig": "4.10.1" + "tsconfig": "4.10.5" }, "peerDependencies": { "redux": "^4.2.1" diff --git a/packages/admin-test-utils/src/after-env.ts b/packages/admin-test-utils/src/after-env.ts index c488bddf0d..d89f23a437 100644 --- a/packages/admin-test-utils/src/after-env.ts +++ b/packages/admin-test-utils/src/after-env.ts @@ -1,2 +1,3 @@ import '@testing-library/jest-dom'; import 'jest-styled-components'; +import 'whatwg-fetch'; diff --git a/packages/admin-test-utils/src/environment.ts b/packages/admin-test-utils/src/environment.ts index c681539450..b7371934db 100644 --- a/packages/admin-test-utils/src/environment.ts +++ b/packages/admin-test-utils/src/environment.ts @@ -57,6 +57,7 @@ window.strapi = { isEnabled: () => false, }, projectType: 'Community', + telemetryDisabled: true, }; /* ------------------------------------------------------------------------------------------------- @@ -163,3 +164,53 @@ Object.defineProperty(window, 'localStorage', { writable: true, value: new LocalStorageMock(), }); + +/* ------------------------------------------------------------------------------------------------- + * PointerEvents + * -----------------------------------------------------------------------------------------------*/ + +/** + * JSDOM doesn't implement PointerEvent so we need to mock our own implementation + * Default to mouse left click interaction + * https://github.com/radix-ui/primitives/issues/1822 + * https://github.com/jsdom/jsdom/pull/2666 + */ +class MockPointerEvent extends Event { + button: number; + + ctrlKey: boolean; + + pointerType: string; + + constructor( + type: string, + props: EventInit & { button?: number; ctrlKey?: boolean; pointerType?: string } + ) { + super(type, props); + this.button = props.button || 0; + this.ctrlKey = props.ctrlKey || false; + this.pointerType = props.pointerType || 'mouse'; + } +} + +Object.defineProperty(window, 'PointerEvent', { + writable: true, + value: MockPointerEvent, +}); + +window.HTMLElement.prototype.scrollIntoView = jest.fn(); +window.HTMLElement.prototype.releasePointerCapture = jest.fn(); +window.HTMLElement.prototype.hasPointerCapture = jest.fn(); + +/* ------------------------------------------------------------------------------------------------- + * Navigator + * -----------------------------------------------------------------------------------------------*/ + +/** + * Navigator is a large object so we only mock the properties we need. + */ +Object.assign(navigator, { + clipboard: { + writeText: jest.fn(), + }, +}); diff --git a/packages/cli/create-strapi-app/package.json b/packages/cli/create-strapi-app/package.json index a1f0dac20d..060b56f469 100644 --- a/packages/cli/create-strapi-app/package.json +++ b/packages/cli/create-strapi-app/package.json @@ -1,9 +1,9 @@ { "name": "create-strapi-app", - "version": "4.10.1", + "version": "4.10.5", "description": "Generate a new Strapi application.", "dependencies": { - "@strapi/generate-new": "4.10.1", + "@strapi/generate-new": "4.10.5", "commander": "8.3.0", "inquirer": "8.2.5" }, @@ -49,8 +49,8 @@ "lint": "run -T eslint ." }, "devDependencies": { - "eslint-config-custom": "4.10.1", - "tsconfig": "4.10.1" + "eslint-config-custom": "4.10.5", + "tsconfig": "4.10.5" }, "engines": { "node": ">=14.19.1 <=18.x.x", diff --git a/packages/cli/create-strapi-starter/package.json b/packages/cli/create-strapi-starter/package.json index 9e899d78f5..253a936feb 100644 --- a/packages/cli/create-strapi-starter/package.json +++ b/packages/cli/create-strapi-starter/package.json @@ -1,6 +1,6 @@ { "name": "create-strapi-starter", - "version": "4.10.1", + "version": "4.10.5", "description": "Generate a new Strapi application.", "keywords": [ "create-strapi-starter", @@ -44,7 +44,7 @@ "lint": "run -T eslint ." }, "dependencies": { - "@strapi/generate-new": "4.10.1", + "@strapi/generate-new": "4.10.5", "chalk": "4.1.2", "ci-info": "3.8.0", "commander": "8.3.0", @@ -54,8 +54,8 @@ "ora": "5.4.1" }, "devDependencies": { - "eslint-config-custom": "4.10.1", - "tsconfig": "4.10.1" + "eslint-config-custom": "4.10.5", + "tsconfig": "4.10.5" }, "engines": { "node": ">=14.19.1 <=18.x.x", diff --git a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js b/packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js index 3affb819d2..03479df8ad 100644 --- a/packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js +++ b/packages/core/admin/admin/src/components/AuthenticatedApp/utils/api.js @@ -1,4 +1,3 @@ -import axios from 'axios'; import { getFetchClient } from '@strapi/helper-plugin'; import checkLatestStrapiVersion from './checkLatestStrapiVersion'; import packageJSON from '../../../../../package.json'; @@ -9,10 +8,12 @@ const { get } = getFetchClient(); const fetchStrapiLatestRelease = async (toggleNotification) => { try { - const { - data: { tag_name }, - } = await axios.get('https://api.github.com/repos/strapi/strapi/releases/latest'); + const res = await fetch('https://api.github.com/repos/strapi/strapi/releases/latest'); + if (!res.ok) { + throw new Error('Failed to fetch latest Strapi version.'); + } + const { tag_name } = await res.json(); const shouldUpdateStrapi = checkLatestStrapiVersion(strapiVersion, tag_name); if (shouldUpdateStrapi && showUpdateNotif) { diff --git a/packages/core/admin/admin/src/components/DragLayer/DragLayer.js b/packages/core/admin/admin/src/components/DragLayer/DragLayer.js new file mode 100644 index 0000000000..6a4e1ea60e --- /dev/null +++ b/packages/core/admin/admin/src/components/DragLayer/DragLayer.js @@ -0,0 +1,53 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { useDragLayer } from 'react-dnd'; +import { Box } from '@strapi/design-system'; + +function getStyle(initialOffset, currentOffset, mouseOffset) { + if (!initialOffset || !currentOffset) { + return { display: 'none' }; + } + + const { x, y } = mouseOffset; + + return { + transform: `translate(${x}px, ${y}px)`, + }; +} + +export function DragLayer({ renderItem }) { + const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = useDragLayer( + (monitor) => ({ + item: monitor.getItem(), + itemType: monitor.getItemType(), + initialOffset: monitor.getInitialSourceClientOffset(), + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + mouseOffset: monitor.getClientOffset(), + }) + ); + + if (!isDragging) { + return null; + } + + return ( + + + {renderItem({ type: itemType, item })} + + + ); +} + +DragLayer.propTypes = { + renderItem: PropTypes.func.isRequired, +}; diff --git a/packages/core/admin/admin/src/components/DragLayer/index.js b/packages/core/admin/admin/src/components/DragLayer/index.js new file mode 100644 index 0000000000..c3e27fec39 --- /dev/null +++ b/packages/core/admin/admin/src/components/DragLayer/index.js @@ -0,0 +1 @@ +export * from './DragLayer'; diff --git a/packages/core/admin/admin/src/components/GuidedTour/Homepage/components/tests/Stepper.test.js b/packages/core/admin/admin/src/components/GuidedTour/Homepage/components/tests/Stepper.test.js index a0ea246d51..c7b54e63ca 100644 --- a/packages/core/admin/admin/src/components/GuidedTour/Homepage/components/tests/Stepper.test.js +++ b/packages/core/admin/admin/src/components/GuidedTour/Homepage/components/tests/Stepper.test.js @@ -96,6 +96,27 @@ describe('GuidedTour Stepper', () => { min-height: 4.0625rem; } + .c4 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 500; + color: #ffffff; + } + + .c5 { + font-weight: 500; + font-size: 1rem; + line-height: 1.25; + color: #32324d; + } + + .c11 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 500; + color: #666687; + } + .c0 { -webkit-align-items: center; -webkit-box-align: center; @@ -142,27 +163,6 @@ describe('GuidedTour Stepper', () => { flex-direction: row; } - .c4 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 500; - color: #ffffff; - } - - .c5 { - font-weight: 500; - font-size: 1rem; - line-height: 1.25; - color: #32324d; - } - - .c11 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 500; - color: #666687; - } -
@@ -351,6 +351,27 @@ describe('GuidedTour Stepper', () => { height: 1.875rem; } + .c6 { + font-weight: 500; + font-size: 1rem; + line-height: 1.25; + color: #32324d; + } + + .c11 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 500; + color: #ffffff; + } + + .c14 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 500; + color: #666687; + } + .c0 { -webkit-align-items: center; -webkit-box-align: center; @@ -397,27 +418,6 @@ describe('GuidedTour Stepper', () => { flex-direction: row; } - .c6 { - font-weight: 500; - font-size: 1rem; - line-height: 1.25; - color: #32324d; - } - - .c11 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 500; - color: #ffffff; - } - - .c14 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 500; - color: #666687; - } - .c5 path { fill: #ffffff; } @@ -601,6 +601,13 @@ describe('GuidedTour Stepper', () => { margin-top: 8px; } + .c6 { + font-weight: 500; + font-size: 1rem; + line-height: 1.25; + color: #32324d; + } + .c0 { -webkit-align-items: center; -webkit-box-align: center; @@ -647,13 +654,6 @@ describe('GuidedTour Stepper', () => { flex-direction: row; } - .c6 { - font-weight: 500; - font-size: 1rem; - line-height: 1.25; - color: #32324d; - } - .c5 path { fill: #ffffff; } diff --git a/packages/core/admin/admin/src/components/GuidedTour/Homepage/tests/index.test.js b/packages/core/admin/admin/src/components/GuidedTour/Homepage/tests/index.test.js index bba15e3506..33f3567d3d 100644 --- a/packages/core/admin/admin/src/components/GuidedTour/Homepage/tests/index.test.js +++ b/packages/core/admin/admin/src/components/GuidedTour/Homepage/tests/index.test.js @@ -120,6 +120,42 @@ describe('GuidedTour Homepage', () => { cursor: pointer; } + .c3 { + font-weight: 600; + font-size: 1.125rem; + line-height: 1.22; + color: #32324d; + } + + .c8 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 500; + color: #ffffff; + } + + .c9 { + font-weight: 500; + font-size: 1rem; + line-height: 1.25; + color: #32324d; + } + + .c22 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 500; + color: #666687; + } + + .c29 { + font-size: 0.75rem; + line-height: 1.33; + font-weight: 600; + line-height: 0; + color: #ffffff; + } + .c1 { -webkit-align-items: stretch; -webkit-box-align: stretch; @@ -214,53 +250,18 @@ describe('GuidedTour Homepage', () => { gap: 8px; } - .c3 { - font-weight: 600; - font-size: 1.125rem; - line-height: 1.22; - color: #32324d; - } - - .c8 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 500; - color: #ffffff; - } - - .c9 { - font-weight: 500; - font-size: 1rem; - line-height: 1.25; - color: #32324d; - } - - .c22 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 500; - color: #666687; - } - - .c29 { - font-size: 0.75rem; - line-height: 1.33; - font-weight: 600; - color: #ffffff; - } - .c27 { position: relative; outline: none; } - .c27 svg { + .c27 > svg { height: 12px; width: 12px; } - .c27 svg > g, - .c27 svg path { + .c27 > svg > g, + .c27 > svg path { fill: #ffffff; } @@ -399,13 +400,13 @@ describe('GuidedTour Homepage', () => { outline: none; } - .c16 svg { + .c16 > svg { height: 12px; width: 12px; } - .c16 svg > g, - .c16 svg path { + .c16 > svg > g, + .c16 > svg path { fill: #ffffff; } diff --git a/packages/core/admin/admin/src/components/GuidedTour/Modal/tests/index.test.js b/packages/core/admin/admin/src/components/GuidedTour/Modal/tests/index.test.js index 650feb42c7..8b02808ff6 100644 --- a/packages/core/admin/admin/src/components/GuidedTour/Modal/tests/index.test.js +++ b/packages/core/admin/admin/src/components/GuidedTour/Modal/tests/index.test.js @@ -117,6 +117,43 @@ describe('', () => { cursor: pointer; } + .c15 { + font-weight: 600; + font-size: 0.6875rem; + line-height: 1.45; + text-transform: uppercase; + color: #4945ff; + } + + .c19 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 500; + color: #ffffff; + } + + .c20 { + font-weight: 600; + font-size: 2rem; + line-height: 1.25; + font-weight: 600; + color: #32324d; + } + + .c24 { + font-size: 0.875rem; + line-height: 1.43; + color: #32324d; + } + + .c28 { + font-size: 0.75rem; + line-height: 1.33; + font-weight: 600; + line-height: 0; + color: #ffffff; + } + .c2 { -webkit-align-items: center; -webkit-box-align: center; @@ -244,54 +281,18 @@ describe('', () => { gap: 8px; } - .c15 { - font-weight: 600; - font-size: 0.6875rem; - line-height: 1.45; - text-transform: uppercase; - color: #4945ff; - } - - .c19 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 500; - color: #ffffff; - } - - .c20 { - font-weight: 600; - font-size: 2rem; - line-height: 1.25; - font-weight: 600; - color: #32324d; - } - - .c24 { - font-size: 0.875rem; - line-height: 1.43; - color: #32324d; - } - - .c28 { - font-size: 0.75rem; - line-height: 1.33; - font-weight: 600; - color: #ffffff; - } - .c8 { position: relative; outline: none; } - .c8 svg { + .c8 > svg { height: 12px; width: 12px; } - .c8 svg > g, - .c8 svg path { + .c8 > svg > g, + .c8 > svg path { fill: #ffffff; } @@ -487,7 +488,7 @@ describe('', () => {
theme.colors.neutral200}; -`; - -const DropdownIconWrapper = styled(Box)` - height: ${32 / 16}rem; - width: ${32 / 16}rem; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - - svg { - height: ${6 / 16}rem; - width: ${11 / 16}rem; - > path { - fill: ${({ theme }) => theme.colors.neutral600}; - } - } -`; - -const ToggleButton = styled.button` - border: none; - background: transparent; - display: block; - width: 100%; - text-align: unset; - padding: 0; -`; - -const DragPreview = ({ displayedValue }) => { - return ( - - - - - - - - - - {displayedValue} - - - - - - - - - - - - - - - - - - - ); -}; - -DragPreview.propTypes = { - displayedValue: PropTypes.string.isRequired, -}; - -export default DragPreview; diff --git a/packages/core/admin/admin/src/content-manager/components/DragLayer/index.js b/packages/core/admin/admin/src/content-manager/components/DragLayer/index.js deleted file mode 100644 index 68433948b5..0000000000 --- a/packages/core/admin/admin/src/content-manager/components/DragLayer/index.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from 'react'; -import { useDragLayer } from 'react-dnd'; -import LayoutDndProvider from '../LayoutDndProvider'; - -import ItemTypes from '../../utils/ItemTypes'; -import CardPreview from '../../pages/ListSettingsView/components/CardPreview'; - -import ComponentPreview from './ComponentDragPreview'; -import { RelationDragPreview } from './RelationDragPreview'; - -const layerStyles = { - position: 'fixed', - pointerEvents: 'none', - zIndex: 100, - left: 0, - top: 0, - width: '100%', - height: '100%', -}; - -function getItemStyles(initialOffset, currentOffset, mouseOffset) { - if (!initialOffset || !currentOffset) { - return { display: 'none' }; - } - - const { x, y } = mouseOffset; - // TODO adjust - const transform = `translate(${x}px, ${y}px)`; - - return { - transform, - WebkitTransform: transform, - }; -} - -const CustomDragLayer = () => { - const { itemType, isDragging, item, initialOffset, currentOffset, mouseOffset } = useDragLayer( - (monitor) => ({ - item: monitor.getItem(), - itemType: monitor.getItemType(), - initialOffset: monitor.getInitialSourceClientOffset(), - currentOffset: monitor.getSourceClientOffset(), - isDragging: monitor.isDragging(), - mouseOffset: monitor.getClientOffset(), - }) - ); - - if (!isDragging) { - return null; - } - - /** - * Because a user may have multiple relations / dynamic zones / repeable fields in the same content type, - * we append the fieldName for the item type to make them unique, however, we then want to extract that - * first type to apply the correct preview. - */ - const [actualType] = itemType.split('_'); - - return ( - -
-
- {[ItemTypes.EDIT_FIELD, ItemTypes.FIELD].includes(itemType) && ( - - )} - {actualType === ItemTypes.COMPONENT && ( - - )} - {actualType === ItemTypes.DYNAMIC_ZONE && ( - - )} - {actualType === ItemTypes.RELATION && ( - - )} -
-
-
- ); -}; - -export default CustomDragLayer; diff --git a/packages/core/admin/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/tests/__snapshots__/index.test.js.snap b/packages/core/admin/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/tests/__snapshots__/index.test.js.snap index 082975a831..af3ff0a236 100644 --- a/packages/core/admin/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/tests/__snapshots__/index.test.js.snap +++ b/packages/core/admin/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/tests/__snapshots__/index.test.js.snap @@ -19,6 +19,22 @@ exports[`DynamicTable / Cellcontent / RelationMultiple renders and matches the s min-width: 20px; } +.c6 { + font-size: 0.75rem; + line-height: 1.33; + font-weight: 600; + line-height: 0; + color: #ffffff; +} + +.c11 { + font-weight: 600; + font-size: 0.6875rem; + line-height: 1.45; + text-transform: uppercase; + color: #666687; +} + .c1 { -webkit-align-items: center; -webkit-box-align: center; @@ -84,21 +100,6 @@ exports[`DynamicTable / Cellcontent / RelationMultiple renders and matches the s flex-direction: row; } -.c6 { - font-size: 0.75rem; - line-height: 1.33; - font-weight: 600; - color: #ffffff; -} - -.c11 { - font-weight: 600; - font-size: 0.6875rem; - line-height: 1.45; - text-transform: uppercase; - color: #666687; -} - .c10 { border-radius: 4px; height: 1.5rem; @@ -109,13 +110,13 @@ exports[`DynamicTable / Cellcontent / RelationMultiple renders and matches the s outline: none; } -.c2 svg { +.c2 > svg { height: 12px; width: 12px; } -.c2 svg > g, -.c2 svg path { +.c2 > svg > g, +.c2 > svg path { fill: #ffffff; } diff --git a/packages/core/admin/admin/src/content-manager/components/DynamicZone/index.js b/packages/core/admin/admin/src/content-manager/components/DynamicZone/index.js index 077f46612d..9c1b928af2 100644 --- a/packages/core/admin/admin/src/content-manager/components/DynamicZone/index.js +++ b/packages/core/admin/admin/src/content-manager/components/DynamicZone/index.js @@ -1,6 +1,6 @@ import React, { memo, useMemo, useState } from 'react'; import get from 'lodash/get'; -import isEqual from 'react-fast-compare'; +import isEqual from 'lodash/isEqual'; import PropTypes from 'prop-types'; import { Box, Flex, VisuallyHidden } from '@strapi/design-system'; import { NotAllowedInput, useNotification } from '@strapi/helper-plugin'; diff --git a/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js b/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js index 9995934c32..6770ba91d4 100644 --- a/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js +++ b/packages/core/admin/admin/src/content-manager/components/FieldComponent/index.js @@ -2,7 +2,7 @@ import React, { memo, useMemo } from 'react'; import PropTypes from 'prop-types'; import size from 'lodash/size'; -import isEqual from 'react-fast-compare'; +import isEqual from 'lodash/isEqual'; import { useIntl } from 'react-intl'; import { NotAllowedInput } from '@strapi/helper-plugin'; diff --git a/packages/core/admin/admin/src/content-manager/components/Inputs/index.js b/packages/core/admin/admin/src/content-manager/components/Inputs/index.js index 6c2fc32eb0..4faf280f31 100644 --- a/packages/core/admin/admin/src/content-manager/components/Inputs/index.js +++ b/packages/core/admin/admin/src/content-manager/components/Inputs/index.js @@ -4,7 +4,7 @@ import { useIntl } from 'react-intl'; import get from 'lodash/get'; import omit from 'lodash/omit'; import take from 'lodash/take'; -import isEqual from 'react-fast-compare'; +import isEqual from 'lodash/isEqual'; import { GenericInput, NotAllowedInput, useLibrary } from '@strapi/helper-plugin'; import { useContentTypeLayout } from '../../hooks'; import { getFieldName } from '../../utils'; diff --git a/packages/core/admin/admin/src/content-manager/components/RelationInput/tests/__snapshots__/RelationInput.test.js.snap b/packages/core/admin/admin/src/content-manager/components/RelationInput/tests/__snapshots__/RelationInput.test.js.snap index bf75e80c24..731f3ead67 100644 --- a/packages/core/admin/admin/src/content-manager/components/RelationInput/tests/__snapshots__/RelationInput.test.js.snap +++ b/packages/core/admin/admin/src/content-manager/components/RelationInput/tests/__snapshots__/RelationInput.test.js.snap @@ -87,6 +87,55 @@ exports[`Content-Manager || RelationInput should render and match snapshot 1`] = padding-top: 8px; } +.c5 { + font-size: 0.75rem; + line-height: 1.33; + font-weight: 600; + color: #32324d; +} + +.c13 { + font-size: 0.75rem; + line-height: 1.33; + color: #4945ff; +} + +.c29 { + font-size: 0.875rem; + line-height: 1.43; + color: #32324d; +} + +.c32 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 600; + color: #006096; +} + +.c37 { + font-size: 0.875rem; + line-height: 1.43; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: #4945ff; +} + +.c40 { + font-size: 0.875rem; + line-height: 1.43; + font-weight: 600; + color: #2f6846; +} + +.c42 { + font-size: 0.75rem; + line-height: 1.33; + color: #666687; +} + .c1 { -webkit-align-items: end; -webkit-box-align: end; @@ -190,59 +239,6 @@ exports[`Content-Manager || RelationInput should render and match snapshot 1`] = justify-content: center; } -.c5 { - font-size: 0.75rem; - line-height: 1.33; - font-weight: 600; - color: #32324d; -} - -.c13 { - font-size: 0.75rem; - line-height: 1.33; - color: #4945ff; -} - -.c29 { - font-size: 0.875rem; - line-height: 1.43; - color: #32324d; -} - -.c32 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 600; - color: #006096; -} - -.c37 { - font-size: 0.875rem; - line-height: 1.43; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: #4945ff; -} - -.c40 { - font-size: 0.875rem; - line-height: 1.43; - font-weight: 600; - color: #2f6846; -} - -.c42 { - font-size: 0.75rem; - line-height: 1.33; - color: #666687; -} - -.c36 path { - fill: #666687; -} - .c12 { border: none; position: relative; @@ -291,18 +287,22 @@ exports[`Content-Manager || RelationInput should render and match snapshot 1`] = border: 2px solid #4945ff; } +.c36 path { + fill: #666687; +} + .c23 { position: relative; outline: none; } -.c23 svg { +.c23 > svg { height: 12px; width: 12px; } -.c23 svg > g, -.c23 svg path { +.c23 > svg > g, +.c23 > svg path { fill: #ffffff; } diff --git a/packages/core/admin/admin/src/content-manager/components/Wysiwyg/WysiwygNav.js b/packages/core/admin/admin/src/content-manager/components/Wysiwyg/WysiwygNav.js index 1ac647563e..6f431af831 100644 --- a/packages/core/admin/admin/src/content-manager/components/Wysiwyg/WysiwygNav.js +++ b/packages/core/admin/admin/src/content-manager/components/Wysiwyg/WysiwygNav.js @@ -1,16 +1,8 @@ import React, { useRef, useState } from 'react'; import PropTypes from 'prop-types'; +import styled from 'styled-components'; import { useIntl } from 'react-intl'; -import { - FocusTrap, - Box, - Button, - IconButtonGroup, - Option, - Select, - Popover, - Flex, -} from '@strapi/design-system'; +import { Button, IconButtonGroup, Option, Select, Popover, Flex } from '@strapi/design-system'; import { Bold, Italic, @@ -33,6 +25,9 @@ import { CustomLinkIconButton, } from './WysiwygStyles'; +/** + * TODO: refactor this mess. + */ const WysiwygNav = ({ disabled, editorRef, @@ -56,68 +51,9 @@ const WysiwygNav = ({ if (disabled || isPreviewMode) { return ( - - - - - - - } /> - } - /> - } - /> - - - } /> - - - {!isExpandMode && ( - - )} - - - ); - } - - return ( - - - - @@ -127,22 +63,10 @@ const WysiwygNav = ({ + } /> + } /> onActionClick('Bold', editorRef)} - id="Bold" - label="Bold" - name="Bold" - icon={} - /> - onActionClick('Italic', editorRef)} - id="Italic" - label="Italic" - name="Italic" - icon={} - /> - onActionClick('Underline', editorRef)} + disabled id="Underline" label="Underline" name="Underline" @@ -150,90 +74,149 @@ const WysiwygNav = ({ /> - } - /> - {visiblePopover && ( - - - - - onActionClick('Strikethrough', editorRef, handleTogglePopover)} - id="Strikethrough" - label="Strikethrough" - name="Strikethrough" - icon={} - /> - onActionClick('BulletList', editorRef, handleTogglePopover)} - id="BulletList" - label="BulletList" - name="BulletList" - icon={} - /> - onActionClick('NumberList', editorRef, handleTogglePopover)} - id="NumberList" - label="NumberList" - name="NumberList" - icon={} - /> - - - onActionClick('Code', editorRef, handleTogglePopover)} - id="Code" - label="Code" - name="Code" - icon={} - /> - { - handleTogglePopover(); - onToggleMediaLib(); - }} - id="Image" - label="Image" - name="Image" - icon={} - /> - onActionClick('Link', editorRef, handleTogglePopover)} - id="Link" - label="Link" - name="Link" - // eslint-disable-next-line jsx-a11y/anchor-is-valid - icon={} - /> - onActionClick('Quote', editorRef, handleTogglePopover)} - id="Quote" - label="Quote" - name="Quote" - icon={} - /> - - - - - )} - + } /> + - {onTogglePreviewMode && ( + {!isExpandMode && ( )} - + ); + } + + return ( + + + + + + onActionClick('Bold', editorRef)} + id="Bold" + label="Bold" + name="Bold" + icon={} + /> + onActionClick('Italic', editorRef)} + id="Italic" + label="Italic" + name="Italic" + icon={} + /> + onActionClick('Underline', editorRef)} + id="Underline" + label="Underline" + name="Underline" + icon={} + /> + + + } + /> + {visiblePopover && ( + + + + onActionClick('Strikethrough', editorRef, handleTogglePopover)} + id="Strikethrough" + label="Strikethrough" + name="Strikethrough" + icon={} + /> + onActionClick('BulletList', editorRef, handleTogglePopover)} + id="BulletList" + label="BulletList" + name="BulletList" + icon={} + /> + onActionClick('NumberList', editorRef, handleTogglePopover)} + id="NumberList" + label="NumberList" + name="NumberList" + icon={} + /> + + + onActionClick('Code', editorRef, handleTogglePopover)} + id="Code" + label="Code" + name="Code" + icon={} + /> + { + handleTogglePopover(); + onToggleMediaLib(); + }} + id="Image" + label="Image" + name="Image" + icon={} + /> + onActionClick('Link', editorRef, handleTogglePopover)} + id="Link" + label="Link" + name="Link" + // eslint-disable-next-line jsx-a11y/anchor-is-valid + icon={} + /> + onActionClick('Quote', editorRef, handleTogglePopover)} + id="Quote" + label="Quote" + name="Quote" + icon={} + /> + + + + )} + + + {onTogglePreviewMode && ( + + )} + ); }; @@ -255,3 +238,17 @@ WysiwygNav.propTypes = { }; export default WysiwygNav; + +const StyledFlex = styled(Flex)` + /* Hide the label, every input needs a label. */ + label { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + } +`; diff --git a/packages/core/admin/admin/src/content-manager/components/Wysiwyg/tests/__snapshots__/index.test.js.snap b/packages/core/admin/admin/src/content-manager/components/Wysiwyg/tests/__snapshots__/index.test.js.snap deleted file mode 100644 index a028baa1e5..0000000000 --- a/packages/core/admin/admin/src/content-manager/components/Wysiwyg/tests/__snapshots__/index.test.js.snap +++ /dev/null @@ -1,1196 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Wysiwyg render and actions buttons should render the Wysiwyg 1`] = ` -.c4 { - border-radius: 4px; - border-style: solid; - border-width: 1px; - border-color: #dcdce4; -} - -.c5 { - background: #f6f6f9; - padding: 8px; -} - -.c12 { - padding-right: 16px; - padding-left: 16px; -} - -.c14 { - padding-left: 12px; -} - -.c19 { - background: #ffffff; - padding: 8px; - border-radius: 4px; - border-color: #dcdce4; - border: 1px solid #dcdce4; - width: 2rem; - height: 2rem; - cursor: pointer; -} - -.c27 { - background: #4945ff; - padding: 8px; - padding-right: 16px; - padding-left: 16px; - border-radius: 4px; - border-color: #4945ff; - border: 1px solid #4945ff; - cursor: pointer; -} - -.c33 { - background: #f6f6f9; - padding: 8px; - border-radius: 4px; -} - -.c35 { - background: #ffffff; - padding: 8px; - border-radius: 4px; - border-color: #dcdce4; - border: 1px solid #dcdce4; - cursor: pointer; -} - -.c0 { - -webkit-align-items: stretch; - -webkit-box-align: stretch; - -ms-flex-align: stretch; - align-items: stretch; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - gap: 4px; -} - -.c1 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - gap: 4px; -} - -.c6 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; -} - -.c7 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; -} - -.c8 { - -webkit-align-items: stretch; - -webkit-box-align: stretch; - -ms-flex-align: stretch; - align-items: stretch; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; -} - -.c20 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - -webkit-box-pack: center; - -webkit-justify-content: center; - -ms-flex-pack: center; - justify-content: center; -} - -.c28 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - gap: 8px; -} - -.c34 { - -webkit-align-items: flex-end; - -webkit-box-align: flex-end; - -ms-flex-align: flex-end; - align-items: flex-end; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - -webkit-box-pack: end; - -webkit-justify-content: flex-end; - -ms-flex-pack: end; - justify-content: flex-end; -} - -.c3 { - font-size: 0.75rem; - line-height: 1.33; - font-weight: 600; - color: #32324d; -} - -.c13 { - font-size: 0.875rem; - line-height: 1.43; - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - color: #666687; -} - -.c30 { - font-size: 0.75rem; - line-height: 1.33; - font-weight: 600; - color: #ffffff; -} - -.c37 { - font-size: 0.875rem; - line-height: 1.43; - color: #32324d; -} - -.c21 { - position: relative; - outline: none; -} - -.c21 svg { - height: 12px; - width: 12px; -} - -.c21 svg > g, -.c21 svg path { - fill: #ffffff; -} - -.c21[aria-disabled='true'] { - pointer-events: none; -} - -.c21:after { - -webkit-transition-property: all; - transition-property: all; - -webkit-transition-duration: 0.2s; - transition-duration: 0.2s; - border-radius: 8px; - content: ''; - position: absolute; - top: -4px; - bottom: -4px; - left: -4px; - right: -4px; - border: 2px solid transparent; -} - -.c21:focus-visible { - outline: none; -} - -.c21:focus-visible:after { - border-radius: 8px; - content: ''; - position: absolute; - top: -5px; - bottom: -5px; - left: -5px; - right: -5px; - border: 2px solid #4945ff; -} - -.c25 { - border: 0; - -webkit-clip: rect(0 0 0 0); - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} - -.c29 { - height: 2rem; - border: 1px solid #dcdce4; - background: #ffffff; -} - -.c29[aria-disabled='true'] { - border: 1px solid #dcdce4; - background: #eaeaef; -} - -.c29[aria-disabled='true'] .c2 { - color: #666687; -} - -.c29[aria-disabled='true'] svg > g,.c29[aria-disabled='true'] svg path { - fill: #666687; -} - -.c29[aria-disabled='true']:active { - border: 1px solid #dcdce4; - background: #eaeaef; -} - -.c29[aria-disabled='true']:active .c2 { - color: #666687; -} - -.c29[aria-disabled='true']:active svg > g,.c29[aria-disabled='true']:active svg path { - fill: #666687; -} - -.c29:hover { - background-color: #f6f6f9; -} - -.c29:active { - background-color: #eaeaef; -} - -.c29 .c2 { - color: #32324d; -} - -.c29 svg > g, -.c29 svg path { - fill: #32324d; -} - -.c9 { - position: relative; - border: 1px solid #dcdce4; - padding-right: 12px; - border-radius: 4px; - background: #ffffff; - overflow: hidden; - min-height: 2rem; - outline: none; - box-shadow: 0; - -webkit-transition-property: border-color,box-shadow,fill; - transition-property: border-color,box-shadow,fill; - -webkit-transition-duration: 0.2s; - transition-duration: 0.2s; -} - -.c9:focus-within { - border: 1px solid #4945ff; - box-shadow: #4945ff 0px 0px 0px 2px; -} - -.c15 { - background: transparent; - border: none; - position: relative; - z-index: 1; -} - -.c15 svg { - height: 0.6875rem; - width: 0.6875rem; -} - -.c15 svg path { - fill: #666687; -} - -.c16 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - background: none; - border: none; -} - -.c16 svg { - width: 0.375rem; -} - -.c10 { - position: absolute; - left: 0; - right: 0; - bottom: 0; - top: 0; - width: 100%; - background: transparent; - border: none; -} - -.c10:focus { - outline: none; -} - -.c10[data-disabled] { - cursor: not-allowed; -} - -.c11 { - width: 100%; -} - -.c23 svg > g, -.c23 svg path { - fill: #8e8ea9; -} - -.c23:hover svg > g, -.c23:hover svg path { - fill: #666687; -} - -.c23:active svg > g, -.c23:active svg path { - fill: #a5a5ba; -} - -.c23[aria-disabled='true'] svg path { - fill: #666687; -} - -.c17 span:first-child button { - border-left: 1px solid #dcdce4; - border-radius: 4px 0 0 4px; -} - -.c17 span:last-child button { - border-radius: 0 4px 4px 0; -} - -.c17 .c22 { - border-radius: 0; - border-left: none; -} - -.c17 .c22 svg path { - fill: #4a4a6a; -} - -.c17 .c22:hover { - background-color: #f6f6f9; -} - -.c17 .c22:hover svg path { - fill: #32324d; -} - -.c17 .c22:active { - background-color: #eaeaef; -} - -.c17 .c22:active svg path { - fill: #212134; -} - -.c17 .c22[aria-disabled='true'] svg path { - fill: #666687; -} - -.c32 { - cursor: auto; - height: 100%; -} - -.c32 .CodeMirror-placeholder { - color: #666687 !important; -} - -.c32 .CodeMirror { - font-size: 0.875rem; - height: 290px; - color: #32324d; - direction: ltr; - font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell, 'Open Sans','Helvetica Neue',sans-serif; -} - -.c32 .CodeMirror-lines { - padding: 12px 16px; -} - -.c32 .CodeMirror-scrollbar-filler, -.c32 .CodeMirror-gutter-filler { - background-color: #ffffff; -} - -.c32 .CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} - -.c32 .CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; - white-space: nowrap; -} - -.c32 .CodeMirror-guttermarker { - color: black; -} - -.c32 .CodeMirror-guttermarker-subtle { - color: #999; -} - -.c32 .CodeMirror-cursor { - border-left: 1px solid black; - border-right: none; - width: 0; -} - -.c32 .CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} - -.c32 .cm-fat-cursor .CodeMirror-cursor { - width: auto; - border: 0 !important; - background: #7e7; -} - -.c32 .cm-fat-cursor-mark { - background-color: rgba(20,255,20,0.5); - -webkit-animation: blink 1.06s steps(1) infinite; - -moz-animation: blink 1.06s steps(1) infinite; - -webkit-animation: blink 1.06s steps(1) infinite; - animation: blink 1.06s steps(1) infinite; -} - -.c32 .cm-animate-fat-cursor { - width: auto; - border: 0; - -webkit-animation: blink 1.06s steps(1) infinite; - -moz-animation: blink 1.06s steps(1) infinite; - -webkit-animation: blink 1.06s steps(1) infinite; - animation: blink 1.06s steps(1) infinite; - background-color: #7e7; -} - -.c32 .cm-tab { - display: inline-block; - -webkit-text-decoration: inherit; - text-decoration: inherit; -} - -.c32 .CodeMirror-rulers { - position: absolute; - left: 0; - right: 0; - top: -50px; - bottom: 0; - overflow: hidden; -} - -.c32 .CodeMirror-ruler { - border-left: 1px solid #ccc; - top: 0; - bottom: 0; - position: absolute; -} - -.c32 .cm-header, -.c32 .cm-strong { - font-weight: bold; -} - -.c32 .cm-em { - font-style: italic; -} - -.c32 .cm-link { - -webkit-text-decoration: underline; - text-decoration: underline; -} - -.c32 .cm-strikethrough { - -webkit-text-decoration: line-through; - text-decoration: line-through; -} - -.c32 .CodeMirror-composing { - border-bottom: 2px solid; -} - -.c32 div.CodeMirror span.CodeMirror-matchingbracket { - color: #0b0; -} - -.c32 div.CodeMirror span.CodeMirror-nonmatchingbracket { - color: #a22; -} - -.c32 .CodeMirror-matchingtag { - background: rgba(255,150,0,0.3); -} - -.c32 .CodeMirror-activeline-background { - background: #e8f2ff; -} - -.c32 .CodeMirror { - position: relative; - overflow: hidden; - background: #ffffff; -} - -.c32 .CodeMirror-scroll { - overflow: scroll !important; - margin-bottom: -50px; - margin-right: -50px; - padding-bottom: 50px; - height: 100%; - outline: none; - position: relative; -} - -.c32 .CodeMirror-sizer { - position: relative; - border-right: 50px solid transparent; -} - -.c32 .CodeMirror-vscrollbar, -.c32 .CodeMirror-hscrollbar, -.c32 .CodeMirror-scrollbar-filler, -.c32 .CodeMirror-gutter-filler { - position: absolute; - z-index: 1; - display: none; - outline: none; -} - -.c32 .CodeMirror-vscrollbar { - right: 0; - top: 0; - overflow-x: hidden; - overflow-y: scroll; -} - -.c32 .CodeMirror-hscrollbar { - bottom: 0; - left: 0; - overflow-y: hidden; - overflow-x: scroll; -} - -.c32 .CodeMirror-scrollbar-filler { - right: 0; - bottom: 0; -} - -.c32 .CodeMirror-lines { - cursor: text; - min-height: 1px; -} - -.c32 .CodeMirror pre.CodeMirror-line, -.c32 .CodeMirror pre.CodeMirror-line-like { - -moz-border-radius: 0; - -webkit-border-radius: 0; - border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: 1.5; - color: inherit; - position: relative; - overflow: visible; - -webkit-tap-highlight-color: transparent; - -webkit-font-variant-ligatures: contextual; - font-variant-ligatures: contextual; -} - -.c32 .CodeMirror pre.CodeMirror-line-like { - z-index: 2; -} - -.c32 .CodeMirror-wrap pre.CodeMirror-line, -.c32 .CodeMirror-wrap pre.CodeMirror-line-like { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} - -.c32 .CodeMirror-linebackground { - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - z-index: 0; -} - -.c32 .CodeMirror-linewidget { - position: relative; - padding: 0.1px; -} - -.c32 .CodeMirror-rtl pre { - direction: rtl; -} - -.c32 .CodeMirror-code { - outline: none; -} - -.c32 .CodeMirror-scroll, -.c32 .CodeMirror-sizer, -.c32 .CodeMirror-gutter, -.c32 .CodeMirror-gutters, -.c32 .CodeMirror-linenumber { - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -.c32 .CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; -} - -.c32 .CodeMirror-cursor { - position: absolute; - pointer-events: none; - border-color: #32324d; -} - -.c32 .CodeMirror-measure pre { - position: static; -} - -.c32 div.CodeMirror-cursors { - visibility: hidden; - position: relative; -} - -.c32 div.CodeMirror-cursors + div { - z-index: 0 !important; -} - -.c32 div.CodeMirror-dragcursors { - visibility: visible; -} - -.c32 .CodeMirror-focused div.CodeMirror-cursors { - visibility: visible; -} - -.c32 .CodeMirror-selected { - background: #dcdce4; -} - -.c32 .CodeMirror-crosshair { - cursor: crosshair; -} - -.c32 .cm-force-border { - padding-right: 0.1px; -} - -.c32 .cm-tab-wrap-hack:after { - content: ''; -} - -.c32 span.CodeMirror-selectedtext { - background: none; -} - -.c32 span { - color: #32324d !important; -} - -.c24 { - padding: 8px; - outline-offset: -2px !important; -} - -.c24 svg { - width: 1.125rem; - height: 1.125rem; -} - -.c18 { - margin-left: 16px; -} - -.c26 { - margin: 0 8px; - padding: 8px; -} - -.c26 svg { - width: 1.125rem; - height: 1.125rem; -} - -.c31 { - position: relative; - height: calc(100% - 48px); -} - -.c36 { - background-color: transparent; - border: none; - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; - align-items: center; -} - -.c36 svg { - margin-left: 8px; -} - -.c36 svg path { - fill: #4a4a6a; - width: 0.75rem; - height: 0.75rem; -} - -
-
- - hello world - -
-
-
-
-
-
-
-
- -
-
-
-
-
-
- - - - - - - - - -
- - - -
- -
-
-
-
- -
-
-
-
- - -
+
+ +
+
+
+
+ +
+
+
+
+ -
+ + + +
Expiration date: Unlimited
-
+ + + +
@@ -3074,56 +2963,56 @@ exports[`ADMIN | Pages | API TOKENS | EditView renders and matches the snapshot

Permissions

Only actions bound by a route are listed below.