diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml index 73ad08922d..40b36f64c8 100644 --- a/.github/workflows/auto-merge.yml +++ b/.github/workflows/auto-merge.yml @@ -1,10 +1,14 @@ name: Auto merge main -> next concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.repository }} cancel-in-progress: true on: + workflow_dispatch: + inputs: + repository: + description: 'Please enter a repository name' push: branches: - 'main' @@ -18,7 +22,7 @@ jobs: with: app-id: ${{ vars.NOCOBASE_APP_ID }} private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} - repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + repositories: nocobase,pro-plugins,plugin-pro-tpl,${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} skip-token-revoke: true - name: Get GitHub App User ID id: get-user-id @@ -28,11 +32,11 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - repository: nocobase/nocobase + repository: nocobase/${{ inputs.repository || 'nocobase' }} token: ${{ steps.app-token.outputs.token }} persist-credentials: true fetch-depth: 0 - - name: main -> next(nocobase) + - name: main -> next(${{ inputs.repository || 'nocobase' }}) run: | git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' @@ -41,11 +45,11 @@ jobs: git checkout next git merge main git push origin next - - name: push nocobase(next) + - name: push ${{ inputs.repository || 'nocobase' }}(next) uses: ad-m/github-push-action@master with: branch: next github_token: ${{ steps.app-token.outputs.token }} - repository: nocobase/nocobase + repository: nocobase/${{ inputs.repository || 'nocobase' }} tags: true atomic: true diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 87b3e58a52..73e9cae91e 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -63,28 +63,28 @@ jobs: username: ${{ secrets.ALI_DOCKER_USERNAME }} password: ${{ secrets.ALI_DOCKER_PASSWORD }} - - name: Login to Aliyun Container Registry (Public) - uses: docker/login-action@v2 - with: - registry: ${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }} - username: ${{ secrets.ALI_DOCKER_USERNAME }} - password: ${{ secrets.ALI_DOCKER_PASSWORD }} - - - name: Login to Docker Hub - if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + # - name: Login to Aliyun Container Registry (Public) + # uses: docker/login-action@v2 + # with: + # registry: ${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }} + # username: ${{ secrets.ALI_DOCKER_USERNAME }} + # password: ${{ secrets.ALI_DOCKER_PASSWORD }} + # + # - name: Login to Docker Hub + # if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' + # uses: docker/login-action@v2 + # with: + # username: ${{ secrets.DOCKERHUB_USERNAME }} + # password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set tags id: set-tags run: | - if [[ "${{ github.ref }}" == "refs/heads/main" ]] || [[ "${{ github.ref }}" == "refs/heads/next" ]]; then - echo "::set-output name=tags::${{ steps.meta.outputs.tags }},${{ secrets.ALI_DOCKER_REGISTRY }}/${{ steps.meta.outputs.tags }},${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/${{ steps.meta.outputs.tags }}" - else + # if [[ "${{ github.ref }}" == "refs/heads/main" ]] || [[ "${{ github.ref }}" == "refs/heads/next" ]]; then + # echo "::set-output name=tags::${{ steps.meta.outputs.tags }},${{ secrets.ALI_DOCKER_REGISTRY }}/${{ steps.meta.outputs.tags }},${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/${{ steps.meta.outputs.tags }}" + # else echo "::set-output name=tags::${{ secrets.ALI_DOCKER_REGISTRY }}/${{ steps.meta.outputs.tags }}" - fi + # fi - name: Build and push uses: docker/build-push-action@v3 diff --git a/.github/workflows/build-pro-image.yml b/.github/workflows/build-pro-image.yml index 6fcdd8bfed..773f619f9e 100644 --- a/.github/workflows/build-pro-image.yml +++ b/.github/workflows/build-pro-image.yml @@ -13,6 +13,7 @@ on: paths: - 'packages/**' - 'Dockerfile.pro' + - 'package.json' - '.github/workflows/build-pro-image.yml' jobs: diff --git a/.github/workflows/changelog-and-release.yml b/.github/workflows/changelog-and-release.yml index ed7d78c231..0c2d0e2484 100644 --- a/.github/workflows/changelog-and-release.yml +++ b/.github/workflows/changelog-and-release.yml @@ -16,18 +16,31 @@ on: default: beta push: tags: - - 'v*-beta' + - 'v*' jobs: write-changelog-and-release: runs-on: ubuntu-latest steps: + - name: Get info + id: get-info + shell: bash + run: | + if [[ "${{ inputs.version }}" == "alpha" || ${{ github.ref_name }} =~ "alpha" ]]; then + echo "branch=$(echo 'next')" >> $GITHUB_OUTPUT + echo "version=$(echo 'alpha')" >> $GITHUB_OUTPUT + echo "proRepos=$(echo '${{ vars.NEXT_PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + else + echo "branch=$(echo 'main')" >> $GITHUB_OUTPUT + echo "version=$(echo 'beta')" >> $GITHUB_OUTPUT + echo "proRepos=$(echo '${{ vars.PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + fi - uses: actions/create-github-app-token@v1 id: app-token with: app-id: ${{ vars.NOCOBASE_APP_ID }} private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} - repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(steps.get-info.outputs.proRepos), ',') }} skip-token-revoke: true - name: Get GitHub App User ID id: get-user-id @@ -38,7 +51,7 @@ jobs: uses: actions/checkout@v4 with: repository: nocobase/nocobase - ref: main + ref: ${{ steps.get-info.outputs.branch }} token: ${{ steps.app-token.outputs.token }} persist-credentials: true fetch-depth: 0 @@ -47,15 +60,16 @@ jobs: with: repository: nocobase/pro-plugins path: packages/pro-plugins + ref: ${{ steps.get-info.outputs.branch }} fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} persist-credentials: true - name: Clone pro repos shell: bash run: | - for repo in ${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ' ') }} + for repo in ${{ join(fromJSON(steps.get-info.outputs.proRepos), ' ') }} do - git clone -b main https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + git clone -b ${{ steps.get-info.outputs.branch }} https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo done - name: Set user run: | @@ -70,11 +84,12 @@ jobs: - name: Run script shell: bash run: | - node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }} --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }} + node scripts/release/changelogAndRelease.js --ver ${{ steps.get-info.outputs.version }} --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }} env: - PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }} + PRO_PLUGIN_REPOS: ${{ steps.get-info.outputs.proRepos }} GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: Commit and push + if: ${{ steps.get-info.outputs.version == 'beta' }} run: | git pull origin main git add . diff --git a/.github/workflows/deploy-client-docs.yml b/.github/workflows/deploy-client-docs.yml index 780b48031d..b823e22722 100644 --- a/.github/workflows/deploy-client-docs.yml +++ b/.github/workflows/deploy-client-docs.yml @@ -44,7 +44,7 @@ jobs: echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}" fi - name: copy files via ssh - ${{ steps.set-tags.outputs.tags }} - uses: appleboy/scp-action@v0.1.4 + uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.CN_CLIENT_HOST }} username: ${{ secrets.CN_CLIENT_USERNAME }} diff --git a/.github/workflows/manual-build-pr-docker-image.yml b/.github/workflows/manual-build-pr-docker-image.yml index c5bf6ba271..0da332c5ca 100644 --- a/.github/workflows/manual-build-pr-docker-image.yml +++ b/.github/workflows/manual-build-pr-docker-image.yml @@ -44,7 +44,7 @@ jobs: uses: docker/build-push-action@v3 with: context: . - file: Dockerfile + file: Dockerfile.pro build-args: | VERDACCIO_URL=http://localhost:4873/ COMMIT_HASH=${GITHUB_SHA} diff --git a/.github/workflows/manual-build-pro-image.yml b/.github/workflows/manual-build-pro-image.yml index 57fe6866e2..b78435fbe2 100644 --- a/.github/workflows/manual-build-pro-image.yml +++ b/.github/workflows/manual-build-pro-image.yml @@ -1,19 +1,18 @@ name: Manual build pro image concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.pr_number }}-${{ inputs.nocobase_pr_number }} cancel-in-progress: true +run-name: Build pro image ${{ github.ref }}-${{ inputs.pr_number }}-${{ inputs.nocobase_pr_number }} + on: workflow_dispatch: inputs: - base_branch: - description: 'Please enter a base branch for main repo' - required: true - default: 'main' pr_number: - description: 'Please enter a pull request number' - required: true + description: 'Please enter the pr number of pro-plugins' + nocobase_pr_number: + description: 'Please enter the pr number of nocobase/nocobase repository' jobs: build-and-push: @@ -43,22 +42,32 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.base_branch }} + ref: ${{ github.head_ref || github.ref_name }} token: ${{ steps.app-token.outputs.token }} submodules: true - - name: Set PR branch - id: set_pro_pr_branch - if: inputs.pr_number != 'main' - run: echo "pr_branch=refs/pull/${{ github.event.inputs.pr_number }}/head" >> $GITHUB_OUTPUT - - name: Echo PR branch - run: echo "${{ steps.set_pro_pr_branch.outputs.pr_branch }}" + - name: Checkout nocobase/nocobase pr + if: ${{ inputs.nocobase_pr_number != '' }} + shell: bash + run: | + gh pr checkout ${{ inputs.nocobase_pr_number }} + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - name: Checkout pro-plugins uses: actions/checkout@v4 with: repository: nocobase/pro-plugins path: packages/pro-plugins - ref: ${{ steps.set_pro_pr_branch.outputs.pr_branch || 'main' }} + ref: ${{ github.head_ref || github.ref_name }} token: ${{ steps.app-token.outputs.token }} + - name: Checkout pr + if: ${{ inputs.pr_number != '' }} + shell: bash + run: | + cd ./packages/pro-plugins/ + gh pr checkout ${{ inputs.pr_number }} + cd ../../ + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - name: Clone pro repos shell: bash run: | @@ -81,23 +90,24 @@ jobs: uses: docker/setup-buildx-action@v2 with: driver-opts: network=host - - name: Docker meta - id: meta - uses: docker/metadata-action@v4 - with: - images: | - nocobase/nocobase - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - name: Login to Aliyun Container Registry uses: docker/login-action@v2 with: registry: ${{ secrets.ALI_DOCKER_REGISTRY }} username: ${{ secrets.ALI_DOCKER_USERNAME }} password: ${{ secrets.ALI_DOCKER_PASSWORD }} + - name: Get tag + id: get-tag + run: | + if [ "${{ inputs.pr_number }}" != "" ]; then + echo "tag=pr-${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT" + else + echo "tag=${{ github.head_ref || github.ref_name }}" >> "$GITHUB_OUTPUT" + fi + - name: Set tags + id: set-tags + run: | + echo "::set-output name=tags::${{ secrets.ALI_DOCKER_REGISTRY }}/nocobase/nocobase:${{ steps.get-tag.outputs.tag }}-pro" - name: Set variables run: | target_directory="./packages/pro-plugins/@nocobase" @@ -110,11 +120,11 @@ jobs: echo "var1=$BEFORE_PACK_NOCOBASE" >> $GITHUB_OUTPUT echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT id: vars - - name: Build and push - pr-${{ inputs.pr_number }}-pro + - name: Build and push - ${{ steps.get-tag.outputs.tag }}-pro uses: docker/build-push-action@v3 with: context: . - file: Dockerfile + file: Dockerfile.pro build-args: | VERDACCIO_URL=http://localhost:4873/ COMMIT_HASH=${GITHUB_SHA} @@ -122,12 +132,12 @@ jobs: BEFORE_PACK_NOCOBASE=${{ steps.vars.outputs.var1 }} APPEND_PRESET_LOCAL_PLUGINS=${{ steps.vars.outputs.var2 }} push: true - tags: ${{ secrets.ALI_DOCKER_REGISTRY }}/nocobase/nocobase:pr-${{ inputs.pr_number }}-pro + tags: ${{ steps.set-tags.outputs.tags }} - name: Deploy NocoBase run: | - curl --retry 2 --location --request POST "${{secrets.NOCOBASE_DEPLOY_HOST}}pr-${{ inputs.pr_number }}-pro" \ + curl --retry 2 --location --request POST "${{secrets.NOCOBASE_DEPLOY_HOST}}${{ steps.get-tag.outputs.tag }}-pro" \ --header 'Content-Type: application/json' \ -d "{ - \"tag\": \"pr-${{ inputs.pr_number }}-pro\", + \"tag\": \"${{ steps.get-tag.outputs.tag }}-pro\", \"dialect\": \"postgres\" }" diff --git a/.github/workflows/manual-build-pro-plugin-image.yml b/.github/workflows/manual-build-pro-plugin-image.yml index 3b87bcaf75..30574a92a0 100644 --- a/.github/workflows/manual-build-pro-plugin-image.yml +++ b/.github/workflows/manual-build-pro-plugin-image.yml @@ -1,9 +1,11 @@ name: Build pro plugin docker image concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.pro_plugin }}-${{ inputs.pr_number }}-${{ inputs.nocobase_pr_number }} cancel-in-progress: true +run-name: Build pro plugin image ${{ github.ref }}-${{ inputs.pro_plugin }}-${{ inputs.pr_number }}-${{ inputs.nocobase_pr_number }} + on: workflow_dispatch: inputs: @@ -107,7 +109,7 @@ jobs: uses: docker/build-push-action@v3 with: context: . - file: Dockerfile + file: Dockerfile.pro build-args: | VERDACCIO_URL=http://localhost:4873/ COMMIT_HASH=${GITHUB_SHA} diff --git a/.github/workflows/manual-release-next.yml b/.github/workflows/manual-release-next.yml new file mode 100644 index 0000000000..c62df15b38 --- /dev/null +++ b/.github/workflows/manual-release-next.yml @@ -0,0 +1,86 @@ +name: Manual release next + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_dispatch: + +jobs: + update-version: + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Get GitHub App User ID + id: get-user-id + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Checkout + uses: actions/checkout@v4 + with: + repository: nocobase/nocobase + token: ${{ steps.app-token.outputs.token }} + persist-credentials: true + fetch-depth: 0 + ref: next + - name: Checkout pro-plugins + uses: actions/checkout@v4 + with: + repository: nocobase/pro-plugins + path: packages/pro-plugins + fetch-depth: 0 + ref: next + token: ${{ steps.app-token.outputs.token }} + persist-credentials: true + - name: Clone pro repos + shell: bash + run: | + for repo in ${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + git clone -b next https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + done + - name: Set Node.js 18 + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install Lerna + run: npm install -g lerna@4 auto-changelog@2 + - name: Run release.sh + shell: bash + run: | + cd ./packages/pro-plugins + git checkout next + for repo in ${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + echo "@nocobase/$repo" >> .git/info/exclude + done + echo "$(<.git/info/exclude )" + cd ./../.. + git checkout next + git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' + echo "packages/pro-plugins" >> .git/info/exclude + bash release.sh + env: + PRO_PLUGIN_REPOS: ${{ vars.NEXT_PRO_PLUGIN_REPOS }} + CUSTOM_PRO_PLUGIN_REPOS: ${{ vars.CUSTOM_PRO_PLUGIN_REPOS }} + - name: Push + run: | + for repo in ${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + cd ./packages/pro-plugins/@nocobase/$repo + git push origin next --atomic --tags + cd ../../../../ + done + cd ./packages/pro-plugins + git push origin next --atomic --tags + cd ../../ + git push origin next --tags --atomic diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 218c17f083..597ce032a2 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -6,10 +6,6 @@ concurrency: on: workflow_dispatch: - inputs: - is_feat: - description: 'is feat' - type: boolean jobs: pre-merge-main-into-next: @@ -110,9 +106,8 @@ jobs: git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' echo "packages/pro-plugins" >> .git/info/exclude - bash release.sh $IS_FEAT + bash release.sh env: - IS_FEAT: ${{ inputs.is_feat && '--is-feat' || '' }} PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }} CUSTOM_PRO_PLUGIN_REPOS: ${{ vars.CUSTOM_PRO_PLUGIN_REPOS }} - name: Push and merge into next diff --git a/.github/workflows/nocobase-test-backend.yml b/.github/workflows/nocobase-test-backend.yml index d9e1ceee4e..7629f793bf 100644 --- a/.github/workflows/nocobase-test-backend.yml +++ b/.github/workflows/nocobase-test-backend.yml @@ -39,8 +39,8 @@ jobs: sqlite-test: strategy: matrix: - node_version: ['20'] - underscored: [true, false] + node_version: [ '20' ] + underscored: [ true, false ] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} services: @@ -70,10 +70,10 @@ jobs: postgres-test: strategy: matrix: - node_version: ['20'] - underscored: [true, false] - schema: [public, nocobase] - collection_schema: [public, user_schema] + node_version: [ '20' ] + underscored: [ true, false ] + schema: [ public, nocobase ] + collection_schema: [ public, user_schema ] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} services: @@ -129,8 +129,8 @@ jobs: mysql-test: strategy: matrix: - node_version: ['20'] - underscored: [true, false] + node_version: [ '20' ] + underscored: [ true, false ] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} services: @@ -175,8 +175,8 @@ jobs: mariadb-test: strategy: matrix: - node_version: ['20'] - underscored: [true, false] + node_version: [ '20' ] + underscored: [ true, false ] runs-on: ubuntu-latest container: node:${{ matrix.node_version }} services: diff --git a/.github/workflows/release-next.yml b/.github/workflows/release-next.yml deleted file mode 100644 index 240075a57f..0000000000 --- a/.github/workflows/release-next.yml +++ /dev/null @@ -1,154 +0,0 @@ -name: Release next - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - workflow_dispatch: - -jobs: - publish-npm: - runs-on: ubuntu-latest - container: node:18 - steps: - - uses: actions/create-github-app-token@v1 - id: app-token - with: - app-id: ${{ vars.NOCOBASE_APP_ID }} - private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} - repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} - skip-token-revoke: true - - name: Get GitHub App User ID - id: get-user-id - run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - - name: Checkout - uses: actions/checkout@v4 - with: - ref: next - fetch-depth: 0 - - name: Send curl request and parse response - env: - PKG_USERNAME: ${{ secrets.PKG_USERNAME }} - PKG_PASSWORD: ${{ secrets.PKG_PASSWORD }} - run: | - mkdir git-ci-cache - apt-get update && apt-get install -y jq gh - response1=$(curl -s 'https://pkg.nocobase.com/-/verdaccio/sec/login' \ - -H 'content-type: application/json' \ - --data-raw '{"username":"'$PKG_USERNAME'","password":"'$PKG_PASSWORD'"}') - token1=$(echo $response1 | jq -r '.token') - response2=$(curl -s 'https://pkg-src.nocobase.com/-/verdaccio/sec/login' \ - -H 'content-type: application/json' \ - --data-raw '{"username":"'$PKG_USERNAME'","password":"'$PKG_PASSWORD'"}') - token2=$(echo $response2 | jq -r '.token') - echo "PKG_NOCOBASE_TOKEN=$token1" >> $GITHUB_ENV - echo "PKG_SRC_NOCOBASE_TOKEN=$token2" >> $GITHUB_ENV - - name: restore cache - id: cache - uses: actions/cache@v3 - with: - path: ./git-ci-cache - key: new-next-version-${{ github.run_id }} - - name: Set NEWVERSION variable - id: set_version - run: | - cd ./git-ci-cache - if [ -f newversion.txt ]; then - NEWVERSION=$(cat newversion.txt) - else - NEWVERSION=$(cat ../lerna.json | jq -r '.version').$(date +'%Y%m%d%H%M%S') - echo "$NEWVERSION" > newversion.txt - fi - echo "NEWVERSION=$NEWVERSION" >> $GITHUB_ENV - - name: Print NEWVERSION - run: echo "The new version is ${{ env.NEWVERSION }}" - - name: Save NEWVERSION to cache - run: echo "NEWVERSION=$NEWVERSION" >> ./git-ci-cache/newversion.txt - - name: save cache - id: save-cache - uses: actions/cache/save@v3 - if: steps.cache.outputs.cache-hit != 'true' - with: - path: ./git-ci-cache - key: new-next-version-${{ github.run_id }} - - name: publish npmjs.org - continue-on-error: true - run: | - git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' - git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' - git config --global --add safe.directory /__w/nocobase/nocobase - npm config set access public - npm config set registry https://registry.npmjs.org/ - npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} - yarn config set access public - yarn config set registry https://registry.npmjs.org/ - yarn config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} - yarn install - yarn lerna version ${{ env.NEWVERSION }} -y --no-git-tag-version - yarn build - echo "# test" >> Release.md - git add . - git commit -m "chore(versions): test publish packages xxx" - cat lerna.json - yarn release:force --no-verify-access --no-git-reset --registry https://registry.npmjs.org/ --dist-tag=next - - name: Checkout pro-plugins - uses: actions/checkout@v3 - with: - repository: nocobase/pro-plugins - path: packages/pro-plugins - ref: next - fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} - - name: Clone pro repos - shell: bash - run: | - for repo in ${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} - do - git clone -b next https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo - done - - name: Build Pro plugins - run: | - yarn config set registry https://registry.npmjs.org/ - yarn install - yarn lerna version ${{ env.NEWVERSION }} -y --no-git-tag-version - yarn build packages/pro-plugins - - name: publish pkg.nocobase.com - run: | - git reset --hard - npm config set //pkg.nocobase.com/:_authToken=${{ env.PKG_NOCOBASE_TOKEN }} - yarn release:force --no-verify-access --no-git-reset --registry https://pkg.nocobase.com --dist-tag=next - - name: publish pkg-src.nocobase.com - run: | - git reset --hard - bash generate-npmignore.sh ignore-src - npm config set //pkg-src.nocobase.com/:_authToken=${{ env.PKG_SRC_NOCOBASE_TOKEN }} - yarn release:force --no-verify-access --no-git-reset --registry https://pkg-src.nocobase.com --dist-tag=next - - name: Tag - run: | - git reset --hard HEAD~ - git tag v${{ env.NEWVERSION }} - git push origin v${{ env.NEWVERSION }} - cd ./packages/pro-plugins - git reset --hard - git tag v${{ env.NEWVERSION }} - git push origin v${{ env.NEWVERSION }} - cd ../../ - for repo in ${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} - do - cd ./packages/pro-plugins/@nocobase/$repo - git reset --hard - git tag v${{ env.NEWVERSION }} - git push origin v${{ env.NEWVERSION }} - cd ../../../../ - done - - name: Run release script - shell: bash - run: | - git fetch - node scripts/release/changelogAndRelease.js --ver alpha --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }} - env: - PRO_PLUGIN_REPOS: ${{ vars.NEXT_PRO_PLUGIN_REPOS }} - GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7e92a8dcdc..d7c6ed9c8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,7 @@ concurrency: cancel-in-progress: true on: + workflow_dispatch: push: tags: - 'v*' @@ -14,15 +15,28 @@ jobs: runs-on: ubuntu-latest container: node:18 steps: + - name: Get info + id: get-info + shell: bash + run: | + if [[ "${{ github.ref_name }}" =~ "beta" ]]; then + echo "defaultTag=$(echo 'latest')" >> $GITHUB_OUTPUT + echo "proRepos=$(echo '${{ vars.PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + else + echo "defaultTag=$(echo 'next')" >> $GITHUB_OUTPUT + echo "proRepos=$(echo '${{ vars.NEXT_PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + fi - uses: actions/create-github-app-token@v1 id: app-token with: app-id: ${{ vars.NOCOBASE_APP_ID }} private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} - repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(steps.get-info.outputs.proRepos), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} skip-token-revoke: true - name: Checkout uses: actions/checkout@v3 + with: + ref: ${{ github.ref_name }} - name: Send curl request and parse response env: PKG_USERNAME: ${{ secrets.PKG_USERNAME }} @@ -61,19 +75,20 @@ jobs: yarn config set registry https://registry.npmjs.org/ yarn config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} npm whoami - yarn release:force --no-verify-access --no-git-reset --registry https://registry.npmjs.org/ + yarn release:force --no-verify-access --no-git-reset --registry https://registry.npmjs.org/ --dist-tag=${{ steps.get-info.outputs.defaultTag }} - name: Checkout pro-plugins uses: actions/checkout@v3 with: repository: nocobase/pro-plugins path: packages/pro-plugins + ref: ${{ github.ref_name }} token: ${{ steps.app-token.outputs.token }} - name: Clone pro repos shell: bash run: | - for repo in ${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + for repo in ${{ join(fromJSON(steps.get-info.outputs.proRepos), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} do - git clone -b main https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + git clone -b ${{ github.ref_name }} https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo done - name: Build Pro plugins run: | @@ -84,17 +99,26 @@ jobs: run: | git reset --hard npm config set //pkg.nocobase.com/:_authToken=${{ env.PKG_NOCOBASE_TOKEN }} - yarn release:force --no-verify-access --no-git-reset --registry https://pkg.nocobase.com + yarn release:force --no-verify-access --no-git-reset --registry https://pkg.nocobase.com --dist-tag=${{ steps.get-info.outputs.defaultTag }} - name: publish pkg-src.nocobase.com run: | git reset --hard bash generate-npmignore.sh ignore-src npm config set //pkg-src.nocobase.com/:_authToken=${{ env.PKG_SRC_NOCOBASE_TOKEN }} - yarn release:force --no-verify-access --no-git-reset --registry https://pkg-src.nocobase.com + yarn release:force --no-verify-access --no-git-reset --registry https://pkg-src.nocobase.com --dist-tag=${{ steps.get-info.outputs.defaultTag }} push-docker: runs-on: ubuntu-latest needs: publish-npm steps: + - name: Get info + id: get-info + shell: bash + run: | + if [[ "${{ github.ref_name }}" =~ "beta" ]]; then + echo "branch=$(echo 'main')" >> $GITHUB_OUTPUT + else + echo "branch=$(echo 'next')" >> $GITHUB_OUTPUT + fi - name: Checkout uses: actions/checkout@v3 - name: Set up QEMU @@ -123,10 +147,21 @@ jobs: registry: ${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }} username: ${{ secrets.ALI_DOCKER_USERNAME }} password: ${{ secrets.ALI_DOCKER_PASSWORD }} - - name: Build and push + - name: Build and push main + if: ${{ steps.get-info.outputs.branch == 'main' }} uses: docker/build-push-action@v3 with: context: ./docker/nocobase + file: ./docker/nocobase/Dockerfile platforms: linux/amd64,linux/arm64 push: true - tags: nocobase/nocobase:latest,${{ steps.meta.outputs.tags }},${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/nocobase/nocobase:latest,${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/${{ steps.meta.outputs.tags }} + tags: nocobase/nocobase:main,nocobase/nocobase:latest,${{ steps.meta.outputs.tags }},${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/nocobase/nocobase:main,${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/nocobase/nocobase:latest,${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/${{ steps.meta.outputs.tags }} + - name: Build and push next + if: ${{ steps.get-info.outputs.branch == 'next' }} + uses: docker/build-push-action@v3 + with: + context: ./docker/nocobase + file: ./docker/nocobase/Dockerfile.next + platforms: linux/amd64,linux/arm64 + push: true + tags: nocobase/nocobase:next,${{ steps.meta.outputs.tags }},${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/nocobase/nocobase:next,${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/${{ steps.meta.outputs.tags }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 572505f637..4aa4ca248b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,216 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v1.3.50-beta](https://github.com/nocobase/nocobase/compare/v1.3.49-beta...v1.3.50-beta) - 2024-11-14 + +### 🐛 Bug Fixes + +- **[client]** Fix issue preventing linkage rule title from being cleared during editing ([#5644](https://github.com/nocobase/nocobase/pull/5644)) by @katherinehhh + +- **[Comments]** Fix data scope setting not working in comment block by @katherinehhh + +## [v1.3.49-beta](https://github.com/nocobase/nocobase/compare/v1.3.48-beta...v1.3.49-beta) - 2024-11-13 + +### 🚀 Improvements + +- **[client]** support one-to-one and many-to-many (array) field to use file collection ([#5637](https://github.com/nocobase/nocobase/pull/5637)) by @mytharcher + +- **[evaluators]** use Formula.js as default evaluator in calculation node ([#5626](https://github.com/nocobase/nocobase/pull/5626)) by @mytharcher + +### 🐛 Bug Fixes + +- **[client]** Fix reset issue that reverts filter button title to default ([#5635](https://github.com/nocobase/nocobase/pull/5635)) by @katherinehhh + +- **[Action: Import records]** Fixed the issue that many-to-many relationship data cannot be imported through the id field ([#5623](https://github.com/nocobase/nocobase/pull/5623)) by @chareice + +## [v1.3.48-beta](https://github.com/nocobase/nocobase/compare/v1.3.47-beta...v1.3.48-beta) - 2024-11-11 + +### 🚀 Improvements + +- **[client]** support hiding menu items ([#5624](https://github.com/nocobase/nocobase/pull/5624)) by @chenos + +- **[server]** add DB_SQL_BENCHMARK environment variable ([#5615](https://github.com/nocobase/nocobase/pull/5615)) by @chareice + +### 🐛 Bug Fixes + +- **[client]** support file collection as target of one-to-many association ([#5619](https://github.com/nocobase/nocobase/pull/5619)) by @mytharcher + +- **[Action: Import records]** Fixed the issue that many-to-many relationship data cannot be imported through the id field ([#5623](https://github.com/nocobase/nocobase/pull/5623)) by @chareice + +## [v1.3.47-beta](https://github.com/nocobase/nocobase/compare/v1.3.46-beta...v1.3.47-beta) - 2024-11-08 + +### 🚀 Improvements + +- **[Authentication]** Optimize error message for sign in and sign up ([#5612](https://github.com/nocobase/nocobase/pull/5612)) by @2013xile + +### 🐛 Bug Fixes + +- **[client]** + - Fix default value issues in subtable ([#5607](https://github.com/nocobase/nocobase/pull/5607)) by @zhangzhonghe + + - Fix issue with fuzzy search support for association fields with string type title field ([#5611](https://github.com/nocobase/nocobase/pull/5611)) by @katherinehhh + +- **[Authentication]** Fix the issue where users can't change password when signing in with a non-password authenticator ([#5609](https://github.com/nocobase/nocobase/pull/5609)) by @2013xile + +## [v1.3.45-beta](https://github.com/nocobase/nocobase/compare/v1.3.44-beta...v1.3.45-beta) - 2024-11-06 + +### 🐛 Bug Fixes + +- **[client]** permission for the association table field in the table is based on the permission of the corresponding association field ([#5569](https://github.com/nocobase/nocobase/pull/5569)) by @katherinehhh + +- **[Action: Export records]** Fix export with i18n ([#5591](https://github.com/nocobase/nocobase/pull/5591)) by @chareice + +- **[Action: Import records]** fix issue with import belongs to association ([#5417](https://github.com/nocobase/nocobase/pull/5417)) by @chareice + +## [v1.3.44-beta](https://github.com/nocobase/nocobase/compare/v1.3.43-beta...v1.3.44-beta) - 2024-11-05 + +### 🎉 New Features + +- **[Auth: OIDC]** Add an option "enable RP-initiated logout" by @2013xile + +### 🐛 Bug Fixes + +- **[client]** Fix filter issue when setting single-select field as title field in association select ([#5581](https://github.com/nocobase/nocobase/pull/5581)) by @katherinehhh + +## [v1.3.43-beta](https://github.com/nocobase/nocobase/compare/v1.3.42-beta...v1.3.43-beta) - 2024-11-05 + +### 🚀 Improvements + +- **[client]** numeric precision can be configured to 8 digits ([#5552](https://github.com/nocobase/nocobase/pull/5552)) by @chenos + +### 🐛 Bug Fixes + +- **[client]** Fix linkage style not updating in form. ([#5539](https://github.com/nocobase/nocobase/pull/5539)) by @sheldon66 + +- **[Auth: API keys]** Fix the URL path for API keys settings page ([#5562](https://github.com/nocobase/nocobase/pull/5562)) by @2013xile + +- **[Mobile]** Fix the issue of preview images being covered by page ([#5535](https://github.com/nocobase/nocobase/pull/5535)) by @zhangzhonghe + +- **[Block: Map]** resolve map rendering in sub-details and incorrect value display for empty fields ([#5526](https://github.com/nocobase/nocobase/pull/5526)) by @katherinehhh + +- **[Collection: Tree]** Fix errors when updating path collection ([#5551](https://github.com/nocobase/nocobase/pull/5551)) by @2013xile + +## [v1.3.42-beta](https://github.com/nocobase/nocobase/compare/v1.3.41-beta...v1.3.42-beta) - 2024-10-28 + +### 🐛 Bug Fixes + +- **[Collection: Tree]** Fix the issue where node paths are not updated when disassociate children ([#5522](https://github.com/nocobase/nocobase/pull/5522)) by @2013xile + +## [v1.3.41-beta](https://github.com/nocobase/nocobase/compare/v1.3.40-beta...v1.3.41-beta) - 2024-10-27 + +### 🚀 Improvements + +- **[Access control]** Optimize performance for large tables in acl ([#5496](https://github.com/nocobase/nocobase/pull/5496)) by @chareice + +## [v1.3.40-beta](https://github.com/nocobase/nocobase/compare/v1.3.39-beta...v1.3.40-beta) - 2024-10-25 + +### 🎉 New Features + +- **[Auth: OIDC]** Add an option for allowing skip ssl verification by @2013xile + +### 🚀 Improvements + +- **[client]** show disabled unchecked checkbox for unselected fields ([#5503](https://github.com/nocobase/nocobase/pull/5503)) by @katherinehhh + +### 🐛 Bug Fixes + +- **[database]** Fix the issue where string operators "contains" and "does not contain do not properly handle `null` values ([#5509](https://github.com/nocobase/nocobase/pull/5509)) by @2013xile + +- **[client]** Fix linkage rule to correctly evaluate URL parameter variables ([#5504](https://github.com/nocobase/nocobase/pull/5504)) by @katherinehhh + +- **[Block: Map]** Fixed the issue where some maps are displayed incorrectly when multiple maps exist due to multiple calls to the `load` method of AMap ([#5490](https://github.com/nocobase/nocobase/pull/5490)) by @Cyx649312038 + +## [v1.3.39-beta](https://github.com/nocobase/nocobase/compare/v1.3.38-beta...v1.3.39-beta) - 2024-10-24 + +### 🐛 Bug Fixes + +- **[client]** Fix the issue where filter blocks cannot be added in the popup ([#5502](https://github.com/nocobase/nocobase/pull/5502)) by @zhangzhonghe + +## [v1.3.38-beta](https://github.com/nocobase/nocobase/compare/v1.3.37-beta...v1.3.38-beta) - 2024-10-24 + +### 🐛 Bug Fixes + +- **[client]** + - pagination issue in list block with simple pagination collection ([#5500](https://github.com/nocobase/nocobase/pull/5500)) by @katherinehhh + + - In non-config mode, display only the current collection in the edit form. ([#5499](https://github.com/nocobase/nocobase/pull/5499)) by @katherinehhh + +- **[Workflow: HTTP request node]** fix special white space appears when paste content into variable textarea caused issue ([#5497](https://github.com/nocobase/nocobase/pull/5497)) by @mytharcher + +- **[Departments]** Fix the issue of incorrect external data source permissions check under the department role by @2013xile + +## [v1.3.37-beta](https://github.com/nocobase/nocobase/compare/v1.3.36-beta...v1.3.37-beta) - 2024-10-23 + +### 🚀 Improvements + +- **[client]** Adjust hint in configuration panel of binding workflow to a button ([#5494](https://github.com/nocobase/nocobase/pull/5494)) by @mytharcher + +### 🐛 Bug Fixes + +- **[File manager]** fix upload and destroy file record within an association block ([#5493](https://github.com/nocobase/nocobase/pull/5493)) by @mytharcher + +## [v1.3.36-beta](https://github.com/nocobase/nocobase/compare/v1.3.35-beta...v1.3.36-beta) - 2024-10-22 + +### 🐛 Bug Fixes + +- **[Collection: Tree]** Fix the issue where the path collection for the inheritance tree collection is not automatically created ([#5486](https://github.com/nocobase/nocobase/pull/5486)) by @2013xile + +- **[Calendar]** show pagination bar with data in the table ([#5480](https://github.com/nocobase/nocobase/pull/5480)) by @katherinehhh + +- **[File manager]** fix file can not be uploaded due to rule hook. ([#5460](https://github.com/nocobase/nocobase/pull/5460)) by @mytharcher + +- **[Collection field: Formula]** Fix incorrect formula calculation in nested multi-level sub-table ([#5469](https://github.com/nocobase/nocobase/pull/5469)) by @gu-zhichao + +## [v1.3.35-beta](https://github.com/nocobase/nocobase/compare/v1.3.34-beta...v1.3.35-beta) - 2024-10-21 + +### 🚀 Improvements + +- **[Workflow: mailer node]** add placeholder to mailer node ([#5470](https://github.com/nocobase/nocobase/pull/5470)) by @mytharcher + +## [v1.3.34-beta](https://github.com/nocobase/nocobase/compare/v1.3.33-beta...v1.3.34-beta) - 2024-10-21 + +### 🎉 New Features + +- **[test]** Association fields in filter forms support configuring whether multiple selection is allowed ([#5451](https://github.com/nocobase/nocobase/pull/5451)) by @zhangzhonghe + +- **[client]** Add a variable named "Parent object" ([#5449](https://github.com/nocobase/nocobase/pull/5449)) by @zhangzhonghe +Reference: [Parent object](https://docs.nocobase.com/handbook/ui/variables#parent-object) +### 🐛 Bug Fixes + +- **[client]** + - Fix URL search params variables not being parsed ([#5454](https://github.com/nocobase/nocobase/pull/5454)) by @zhangzhonghe + + - Fix data clearing bug when selecting association data with data scope in nested sub-tables ([#5441](https://github.com/nocobase/nocobase/pull/5441)) by @katherinehhh + + - fix selected background color of table row ([#5445](https://github.com/nocobase/nocobase/pull/5445)) by @mytharcher + +- **[Block: Map]** remove zoom level configuration for map fields in table column ([#5457](https://github.com/nocobase/nocobase/pull/5457)) by @katherinehhh + +- **[File manager]** fix calling storage rule hook on read-pretty fields ([#5447](https://github.com/nocobase/nocobase/pull/5447)) by @mytharcher + +- **[Data source: Main]** fix e2e case failed due to component changed ([#5437](https://github.com/nocobase/nocobase/pull/5437)) by @mytharcher + +## [v1.3.33-beta](https://github.com/nocobase/nocobase/compare/v1.3.32-beta...v1.3.33-beta) - 2024-10-16 + +### 🚀 Improvements + +- **[Workflow]** add association field related hint to the batch mode of update node ([#5426](https://github.com/nocobase/nocobase/pull/5426)) by @mytharcher + +### 🐛 Bug Fixes + +- **[client]** + - fix the issue of Edit profile drawer being covered by subpage ([#5409](https://github.com/nocobase/nocobase/pull/5409)) by @zhangzhonghe + + - Workflow node variables do not display inherited collection fields ([#5415](https://github.com/nocobase/nocobase/pull/5415)) by @chenos + + - pagination not resetting after clearing filter data in table filtering block ([#5411](https://github.com/nocobase/nocobase/pull/5411)) by @katherinehhh + +- **[File manager]** remove the 20 items limit of loading storages in file template collection configuration ([#5430](https://github.com/nocobase/nocobase/pull/5430)) by @mytharcher + +- **[Action: Duplicate record]** Fix the issue where the bulk edit popup does not display content ([#5412](https://github.com/nocobase/nocobase/pull/5412)) by @zhangzhonghe + +- **[Data visualization]** Fix the issue of default values not displaying in the chart filter block ([#5405](https://github.com/nocobase/nocobase/pull/5405)) by @zhangzhonghe + ## [v1.3.32-beta](https://github.com/nocobase/nocobase/compare/v1.3.31-beta...v1.3.32-beta) - 2024-10-13 ### 🐛 Bug Fixes diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index c2f6095cee..d0f0c3fb7a 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -5,6 +5,216 @@ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), 并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。 +## [v1.3.50-beta](https://github.com/nocobase/nocobase/compare/v1.3.49-beta...v1.3.50-beta) - 2024-11-14 + +### 🐛 修复 + +- **[client]** 修复联动规则标题编辑时无法清空的问题 ([#5644](https://github.com/nocobase/nocobase/pull/5644)) by @katherinehhh + +- **[评论]** 修复评论区块设置数据范围不生效问题 by @katherinehhh + +## [v1.3.49-beta](https://github.com/nocobase/nocobase/compare/v1.3.48-beta...v1.3.49-beta) - 2024-11-13 + +### 🚀 优化 + +- **[client]** 一对一字段和多对多(数组)字段支持选择文件表 ([#5637](https://github.com/nocobase/nocobase/pull/5637)) by @mytharcher + +- **[evaluators]** 将运算节点的默认计算引擎更换为 Formula.js ([#5626](https://github.com/nocobase/nocobase/pull/5626)) by @mytharcher + +### 🐛 修复 + +- **[client]** 修复筛选按钮重置后标题恢复为默认名称的问题 ([#5635](https://github.com/nocobase/nocobase/pull/5635)) by @katherinehhh + +- **[操作:导入记录]** 修复无法通过 id 字段导入多对多关联数据的问题 ([#5623](https://github.com/nocobase/nocobase/pull/5623)) by @chareice + +## [v1.3.48-beta](https://github.com/nocobase/nocobase/compare/v1.3.47-beta...v1.3.48-beta) - 2024-11-11 + +### 🚀 优化 + +- **[client]** 支持隐藏菜单项 ([#5624](https://github.com/nocobase/nocobase/pull/5624)) by @chenos + +- **[server]** 增加 DB_SQL_BENCHMARK 环境变量 ([#5615](https://github.com/nocobase/nocobase/pull/5615)) by @chareice + +### 🐛 修复 + +- **[client]** 支持一对多关系使用文件表 ([#5619](https://github.com/nocobase/nocobase/pull/5619)) by @mytharcher + +- **[操作:导入记录]** 修复无法通过 id 字段导入多对多关联数据的问题 ([#5623](https://github.com/nocobase/nocobase/pull/5623)) by @chareice + +## [v1.3.47-beta](https://github.com/nocobase/nocobase/compare/v1.3.46-beta...v1.3.47-beta) - 2024-11-08 + +### 🚀 优化 + +- **[用户认证]** 优化登录、注册的错误提示 ([#5612](https://github.com/nocobase/nocobase/pull/5612)) by @2013xile + +### 🐛 修复 + +- **[client]** + - 修复子表格中的默认值问题 ([#5607](https://github.com/nocobase/nocobase/pull/5607)) by @zhangzhonghe + + - 修复 关系字段标题字段为string 类型时应支持模糊查询 ([#5611](https://github.com/nocobase/nocobase/pull/5611)) by @katherinehhh + +- **[用户认证]** 修复用户使用非密码认证器登录时无法修改密码的问题 ([#5609](https://github.com/nocobase/nocobase/pull/5609)) by @2013xile + +## [v1.3.45-beta](https://github.com/nocobase/nocobase/compare/v1.3.44-beta...v1.3.45-beta) - 2024-11-06 + +### 🐛 修复 + +- **[client]** 表格中关系表字段权限为该关系字段的权限 ([#5569](https://github.com/nocobase/nocobase/pull/5569)) by @katherinehhh + +- **[操作:导出记录]** 修复导出过程中的多语言问题 ([#5591](https://github.com/nocobase/nocobase/pull/5591)) by @chareice + +- **[操作:导入记录]** 修复无法导入多对一关联的问题 ([#5417](https://github.com/nocobase/nocobase/pull/5417)) by @chareice + +## [v1.3.44-beta](https://github.com/nocobase/nocobase/compare/v1.3.43-beta...v1.3.44-beta) - 2024-11-05 + +### 🎉 新特性 + +- **[认证:OIDC]** 添加「启用 RP-initiated logout」选项 by @2013xile + +### 🐛 修复 + +- **[client]** 修复 关系字段下拉选项中设置单选字段为标题字段时筛选不生效的问题 ([#5581](https://github.com/nocobase/nocobase/pull/5581)) by @katherinehhh + +## [v1.3.43-beta](https://github.com/nocobase/nocobase/compare/v1.3.42-beta...v1.3.43-beta) - 2024-11-05 + +### 🚀 优化 + +- **[client]** 数字精确度支持配置 8 位数 ([#5552](https://github.com/nocobase/nocobase/pull/5552)) by @chenos + +### 🐛 修复 + +- **[client]** 修复联动样式在表单里不更新。 ([#5539](https://github.com/nocobase/nocobase/pull/5539)) by @sheldon66 + +- **[认证:API 密钥]** 修复 API keys 设置页面的 URL 路径 ([#5562](https://github.com/nocobase/nocobase/pull/5562)) by @2013xile + +- **[移动端]** 修复预览图片被页面覆盖的问题 ([#5535](https://github.com/nocobase/nocobase/pull/5535)) by @zhangzhonghe + +- **[区块:地图]** 子详情中地图字段,渲染地图不正确,应该显示坐标字符/详情区块,没有值的字段,会显示上一条数据的值 ([#5526](https://github.com/nocobase/nocobase/pull/5526)) by @katherinehhh + +- **[数据表:树]** 修复更新路径表时的报错 ([#5551](https://github.com/nocobase/nocobase/pull/5551)) by @2013xile + +## [v1.3.42-beta](https://github.com/nocobase/nocobase/compare/v1.3.41-beta...v1.3.42-beta) - 2024-10-28 + +### 🐛 修复 + +- **[数据表:树]** 修复解除关联子节点,节点路径没有更新的问题 ([#5522](https://github.com/nocobase/nocobase/pull/5522)) by @2013xile + +## [v1.3.41-beta](https://github.com/nocobase/nocobase/compare/v1.3.40-beta...v1.3.41-beta) - 2024-10-27 + +### 🚀 优化 + +- **[权限控制]** 优化权限系统中的大表查询性能 ([#5496](https://github.com/nocobase/nocobase/pull/5496)) by @chareice + +## [v1.3.40-beta](https://github.com/nocobase/nocobase/compare/v1.3.39-beta...v1.3.40-beta) - 2024-10-25 + +### 🎉 新特性 + +- **[认证:OIDC]** 添加“跳过 SSL 验证“选项 by @2013xile + +### 🚀 优化 + +- **[client]** 勾选字段未勾选时显示禁用的未勾选框 ([#5503](https://github.com/nocobase/nocobase/pull/5503)) by @katherinehhh + +### 🐛 修复 + +- **[database]** 修复字符串操作符”包含“和”不包含“没有正确处理 `null` 值的问题 ([#5509](https://github.com/nocobase/nocobase/pull/5509)) by @2013xile + +- **[client]** 修复联动规则中使用「URL参数变量」作条件判断无效 ([#5504](https://github.com/nocobase/nocobase/pull/5504)) by @katherinehhh + +- **[区块:地图]** 修复高德地图多次调用 `load` 方法,导致多张地图存在时,部分地图展示报错的问题 ([#5490](https://github.com/nocobase/nocobase/pull/5490)) by @Cyx649312038 + +## [v1.3.39-beta](https://github.com/nocobase/nocobase/compare/v1.3.38-beta...v1.3.39-beta) - 2024-10-24 + +### 🐛 修复 + +- **[client]** 修复弹窗中无法添加筛选区块的问题 ([#5502](https://github.com/nocobase/nocobase/pull/5502)) by @zhangzhonghe + +## [v1.3.38-beta](https://github.com/nocobase/nocobase/compare/v1.3.37-beta...v1.3.38-beta) - 2024-10-24 + +### 🐛 修复 + +- **[client]** + - 使用简单分页的数据表在列表区块上分页异常 ([#5500](https://github.com/nocobase/nocobase/pull/5500)) by @katherinehhh + + - 在非配置状态下,编辑表单应只显示本表区块 ([#5499](https://github.com/nocobase/nocobase/pull/5499)) by @katherinehhh + +- **[工作流:HTTP 请求节点]** 修复变量文本输入框中在粘贴时可能产生非标准空格导致服务端逻辑错误的问题 ([#5497](https://github.com/nocobase/nocobase/pull/5497)) by @mytharcher + +- **[部门]** 修复在所属部门角色下外部数据源权限判断不正确的问题 by @2013xile + +## [v1.3.37-beta](https://github.com/nocobase/nocobase/compare/v1.3.36-beta...v1.3.37-beta) - 2024-10-23 + +### 🚀 优化 + +- **[client]** 调整绑定工作流配置面板中的提示文案 ([#5494](https://github.com/nocobase/nocobase/pull/5494)) by @mytharcher + +### 🐛 修复 + +- **[文件管理器]** 修复文件表在关联区块内无法上传和删除记录的问题 ([#5493](https://github.com/nocobase/nocobase/pull/5493)) by @mytharcher + +## [v1.3.36-beta](https://github.com/nocobase/nocobase/compare/v1.3.35-beta...v1.3.36-beta) - 2024-10-22 + +### 🐛 修复 + +- **[数据表:树]** 修复继承的树表没有自动创建路径表的问题 ([#5486](https://github.com/nocobase/nocobase/pull/5486)) by @2013xile + +- **[日历]** 当表格有数据时分页器应该显示 ([#5480](https://github.com/nocobase/nocobase/pull/5480)) by @katherinehhh + +- **[文件管理器]** 修复由于上传规则 hook 改动导致文件无法上传的问题 ([#5460](https://github.com/nocobase/nocobase/pull/5460)) by @mytharcher + +- **[数据表字段:公式]** 修复 多层子表格嵌套时,公式计算结果的错误 ([#5469](https://github.com/nocobase/nocobase/pull/5469)) by @gu-zhichao + +## [v1.3.35-beta](https://github.com/nocobase/nocobase/compare/v1.3.34-beta...v1.3.35-beta) - 2024-10-21 + +### 🚀 优化 + +- **[工作流:邮件发送节点]** 为邮件节点的表单项增加占位提示内容 ([#5470](https://github.com/nocobase/nocobase/pull/5470)) by @mytharcher + +## [v1.3.34-beta](https://github.com/nocobase/nocobase/compare/v1.3.33-beta...v1.3.34-beta) - 2024-10-21 + +### 🎉 新特性 + +- **[test]** 筛选表单中的关系字段支持配置是否多选 ([#5451](https://github.com/nocobase/nocobase/pull/5451)) by @zhangzhonghe + +- **[client]** 添加一个名为“上级对象”的变量 ([#5449](https://github.com/nocobase/nocobase/pull/5449)) by @zhangzhonghe +参考文档:[上级对象](https://docs-cn.nocobase.com/handbook/ui/variables#%E4%B8%8A%E7%BA%A7%E5%AF%B9%E8%B1%A1) +### 🐛 修复 + +- **[client]** + - 修复 URL 查询参数变量不会被解析的问题 ([#5454](https://github.com/nocobase/nocobase/pull/5454)) by @zhangzhonghe + + - 多层关系下的子表格中关系字段设置数据范围后,选择关系数据后其他行记录被清空 ([#5441](https://github.com/nocobase/nocobase/pull/5441)) by @katherinehhh + + - 修复表格行选中时的背景颜色 ([#5445](https://github.com/nocobase/nocobase/pull/5445)) by @mytharcher + +- **[区块:地图]** 表格中的地图字段不应该有缩放等级配置项 ([#5457](https://github.com/nocobase/nocobase/pull/5457)) by @katherinehhh + +- **[文件管理器]** 屏蔽阅读模式下附件字段对存储规则不必要的查询 ([#5447](https://github.com/nocobase/nocobase/pull/5447)) by @mytharcher + +- **[数据源:主数据库]** 修复由于更换组件导致的 E2E 测试不通过 ([#5437](https://github.com/nocobase/nocobase/pull/5437)) by @mytharcher + +## [v1.3.33-beta](https://github.com/nocobase/nocobase/compare/v1.3.32-beta...v1.3.33-beta) - 2024-10-16 + +### 🚀 优化 + +- **[工作流]** 对更新数据节点的批量模式增加关于关系字段的提示 ([#5426](https://github.com/nocobase/nocobase/pull/5426)) by @mytharcher + +### 🐛 修复 + +- **[client]** + - 修复个人资料配置弹窗被子页面遮挡住的问题 ([#5409](https://github.com/nocobase/nocobase/pull/5409)) by @zhangzhonghe + + - 工作流节点变量不显示继承表字段 ([#5415](https://github.com/nocobase/nocobase/pull/5415)) by @chenos + + - 使用筛选区块筛选表格数据时,清空筛选数据查询数据分页器没有跟着调整 ([#5411](https://github.com/nocobase/nocobase/pull/5411)) by @katherinehhh + +- **[文件管理器]** 移除文件表选择存储空间时仅加载 20 个的限制 ([#5430](https://github.com/nocobase/nocobase/pull/5430)) by @mytharcher + +- **[操作:复制记录]** 修复批量编辑弹窗不显示内容的问题 ([#5412](https://github.com/nocobase/nocobase/pull/5412)) by @zhangzhonghe + +- **[数据可视化]** 修复图表筛选区块中不显示默认值的问题 ([#5405](https://github.com/nocobase/nocobase/pull/5405)) by @zhangzhonghe + ## [v1.3.32-beta](https://github.com/nocobase/nocobase/compare/v1.3.31-beta...v1.3.32-beta) - 2024-10-13 ### 🐛 修复 diff --git a/Dockerfile.pro b/Dockerfile.pro index cb4d035223..7568d7736f 100644 --- a/Dockerfile.pro +++ b/Dockerfile.pro @@ -41,7 +41,7 @@ RUN cd /app \ && tar -zcf ./nocobase.tar.gz -C /app/my-nocobase-app . FROM node:20.13-bullseye-slim -RUN apt-get update && apt-get install -y nginx +RUN apt-get update && apt-get install -y nginx libaio1 RUN rm -rf /etc/nginx/sites-enabled/default COPY ./docker/nocobase/nocobase.conf /etc/nginx/sites-enabled/nocobase.conf diff --git a/LICENSE.txt b/LICENSE.txt index 5f83453444..756cbeee6d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,8 @@ -Updated Date: September 17, 2024 +Updated Date: October 15, 2024 NocoBase License Agreement -NOCOBASE PTE. LTD.,a Singaporean Exempt Private Company Limited by Shares with its principal place of business located at #03-01 ROBINSON 112 ("The Company") https://www.nocobase.com/ issues this License Agreement ("Agreement") to you. You, as an individual or a company ("User"), will be deemed to voluntarily accept all terms of this Agreement by using NocoBase (including but not limited to obtaining NocoBase source code or installation package in any form, installing and using NocoBase, purchasing NocoBase commercial license and services, purchasing NocoBase commercial plugins). If the User does not agree to any term of this Agreement, or cannot accurately understand our interpretation of the relevant terms, please stop using it immediately. +NOCOBASE PTE. LTD.,a Singaporean Exempt Private Company Limited by Shares with its principal place of business located at #03-01 ROBINSON 112 ("The Company") https://www.nocobase.com/ issues this License Agreement ("Agreement") to you. You, as an individual or a company ("the User"), will be deemed to voluntarily accept all terms of this Agreement by using NocoBase (including but not limited to obtaining NocoBase source code or installation package in any form, installing and using NocoBase, purchasing NocoBase commercial license and services, purchasing NocoBase commercial plugins). If the User does not agree to any term of this Agreement, or cannot accurately understand our interpretation of the relevant terms, please stop using it immediately. This Agreement applies to any use, quotation, contract, invoice, and all software delivered by the Company. The User and the Company or NocoBase's agents can no longer sign a separate license agreement for the sale and delivery of the software. @@ -14,15 +14,19 @@ The Company reserves the right to formulate and modify this Agreement from time 1.1 "Software" refers to the NocoBase kernel and plugins placed in the same code repository as the kernel, including their source code, installation packages, images, and all their modifications, updates, and upgrades. -1.2 "Marketplace" refers to the marketplace provided by the Company for selling Software plugins and solutions. +1.2 "Community Edition" refers to the free version of the Software provided to the User through public channels. -1.3 "Commercial Plugin" refers to the paid plugins sold in the Marketplace. +1.3 "Commercial Edition" refers to the paid version of the Software purchased by the User from the Company or its agents, downloaded through exclusive channels, and includes additional benefits. It consists of three versions: Standard Edition, Professional Edition, and Enterprise Edition. -1.4 "Upper Layer Application" refers to a specific business use case application serving internal or external customers of the User, developed based on Software and Commercial Plugins, such as ERP/CRM. +1.4 "Marketplace" refers to the marketplace provided by the Company for selling Software plugins and solutions. -1.5 "Customer" refers to the clients who purchase the User's Upper Layer Application. +1.5 "Commercial Plugin" refers to the paid plugins sold in the Marketplace. -1.6 "Third-Party Open Source Software" refers to open source software provided with Software and Commercial Plugins. They are licensed through various published open source software licenses or copyright notices accompanying such software. +1.6 "Upper Layer Application" refers to a specific business use case application serving internal or external customers of the User, developed based on Software and Commercial Plugins, such as ERP/CRM. + +1.7 "Customer" refers to the clients who purchase the User's Upper Layer Application. + +1.8 "Third-Party Open Source Software" refers to open source software provided with Software and Commercial Plugins. They are licensed through various published open source software licenses or copyright notices accompanying such software. =================================== 2. Intellectual Property Protection @@ -34,7 +38,7 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr 3. Disclaimer ============= -3.1 Users shall not use the Software and Commercial Plugins to engage in activities that violate local laws and regulations, religious beliefs. All legal responsibilities and legal consequences arising from the use of Software and Commercial Plugins shall be borne by the User. +3.1 The User shall not use the Software and Commercial Plugins to engage in activities that violate local laws and regulations, religious beliefs. All legal responsibilities and legal consequences arising from the use of Software and Commercial Plugins shall be borne by the User. 3.2 The Company shall not be liable for any direct, indirect, special, incidental, or consequential damages (including but not limited to loss of profits, business interruption, data loss, or business information disclosure) caused by the User's use of the Software and Commercial Plugins, even if it has been previously informed of the possibility of such damages. @@ -44,7 +48,11 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr 4.1 The Software uses a dual license type of AGPL-3.0 Open Source License https://www.gnu.org/licenses/agpl-3..en.htm and Commercial License. -4.2 Commercial Plugins use Commercial License. +4.2 The Community Edition uses the AGPL-3.0 Open Source License https://www.gnu.org/licenses/agpl-3.0.en.html. + +4.3 The Commercial Edition (including Standard, Professional, and Enterprise Editions) uses the Commercial License. + +4.4 Commercial Plugins use the Commercial Plugin License. ================================================ 5. Rights and Obligations of Open Source License @@ -52,9 +60,9 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr 5.1 The Software can be used for commercial purposes. -5.2 User can sell plugins developed for the Software in the Marketplace. +5.2 The User can sell plugins developed for the Software in the Marketplace. -5.3 Outside the Marketplace, changes and plugins to the Software developed by User or third parties, and third-party applications developed based on the Software must all be open-sourced under the AGPL-3.0 license. +5.3 Outside the Marketplace, changes and plugins to the Software developed by the User or third parties, and third-party applications developed based on the Software must all be open-sourced under the AGPL-3.0 license. 5.4 It is not allowed to remove or change the brand, name, link, version number, license, and other information about NocoBase on the Software interface, except for the main LOGO in the upper left corner of the page. @@ -68,11 +76,11 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr 6. Rights of Commercial License =============================== -6.1 Obtain a permanent commercial license of the Software or Commercial Plugin. +6.1 Obtain a permanent commercial license of the Software. 6.2 Get 12 months of upgrade and exclusive technical support. -6.3 The licensed Software and Commercial Plugins can be used for commercial purposes with no restrictions on the number of applications and users. +6.3 The licensed Software can be used for commercial purposes with no restrictions on the number of applications and users. 6.4 Changes and plugins to the Software, and applications integrated with the Software do not need to be open sourced. @@ -80,7 +88,7 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr 6.6 Can sell plugins developed for Software in the Marketplace. -6.7 Users with a Professional or Enterprise Edition license can sell Upper Layer Application to their clients. +6.7 The User with a Professional or Enterprise Edition License can sell Upper Layer Application to their clients. 6.8 Not restricted by the AGPL-3.0 agreement. @@ -98,26 +106,54 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr 7.4 It is not allowed to provide any form of no-code, zero-code, low-code platform SaaS products to the public using the original or modified Software. -7.5 It is not allowed for Users with a standard license to sell Upper Layer Application to clients without a commercial license. +7.5 It is not allowed for the User with a Standard Edition license to sell Upper Layer Application to clients without a Commercial license. -7.6 It is not allowed to use reverse engineering, decompilation, and other means to try to discover the source code of Commercial Plugins that have not obtained source code license. +7.6 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace. -7.7 It is not allowed to disclose the source code of Commercial Plugins to any third party. +7.7 If there is a violation of the above obligations or the terms of this Agreement, the rights owned by the User will be immediately terminated, the paid fees will not be refunded, and the Company reserves the right to pursue the User's legal responsibility. -7.8 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace. +7.8 If there are other agreements in the contract for the above obligations, the contract agreement shall prevail. -7.9 If there is a violation of the above obligations or the terms of this Agreement, the rights owned by the User will be immediately terminated, the paid fees will not be refunded, and the Company reserves the right to pursue the User's legal responsibility. +====================================== +8. Rights of Commercial Plugin License +====================================== -7.10 If there are other agreements in the contract for the above obligations, the contract agreement shall prevail. +8.1 Obtain a permanent Commercial Plugin License for the Commercial Plugin. + +8.2 Receive 12 months of upgrades and exclusive technical support. + +8.3 Can be used for commercial purposes without restrictions on the number of applications or users. + +8.4 The User with a Professional or Enterprise Edition License can use the Commercial Plugin in Upper Layer Applications sold to their customers. + +8.5 Not restricted by the AGPL-3.0 agreement. + +8.6 If there are other agreements in the contract regarding the above rights, the contract agreement shall prevail. + +=========================================== +9. Obligations of Commercial Plugin License +=========================================== + +9.1 It is not allowed to remove or change any intellectual property statements about NocoBase and the plugin authors in the code. + +9.2 It is not allowed to sell, transfer, lease, share, or gift the Commercial Plugin. + +9.3 It is not allowed to use reverse engineering, decompilation, or other methods to attempt to discover the source code of Commercial Plugins without obtaining a source code license. + +9.4 It is not allowed to disclose the source code of Commercial Plugins to any third party. + +9.5 If there is any violation of the above obligations or the terms of this Agreement, the rights owned by the User will be immediately terminated, the paid fees will not be refunded, and the Company reserves the right to pursue the User's legal responsibility. + +9.6 If there are other agreements in the contract regarding the above obligations, the contract agreement shall prevail. ============================================================= -8. Legal Jurisdiction, Interpretation, and Dispute Resolution +10. Legal Jurisdiction, Interpretation, and Dispute Resolution ============================================================= -8.1 Except for Mainland China, the interpretation, application, and all matters related to this agreement are subject to the jurisdiction of Singapore law. +10.1 Except for Mainland China, the interpretation, application, and all matters related to this agreement are subject to the jurisdiction of Singapore law. -8.2 Any dispute related to this Agreement should first be resolved through friendly negotiation. If the negotiation fails to resolve the dispute, the dispute should be submitted to the International Chamber of Commerce (ICC) for arbitration. The arbitration venue should be Singapore, conducted in English. +10.2 Any dispute related to this Agreement should first be resolved through friendly negotiation. If the negotiation fails to resolve the dispute, the dispute should be submitted to the International Chamber of Commerce (ICC) for arbitration. The arbitration venue should be Singapore, conducted in English. -8.3 All terms and conditions of this Agreement shall be deemed enforceable to the maximum extent permitted by applicable law. If any term of this Agreement is deemed invalid by any applicable law, the invalidity of that term does not affect the validity of any other term of this Agreement, and it should be deemed that the invalid term has been modified as much as possible to make it valid and enforceable, or if the term cannot be modified, it should be deemed to have been deleted from this Agreement. +10.3 All terms and conditions of this Agreement shall be deemed enforceable to the maximum extent permitted by applicable law. If any term of this Agreement is deemed invalid by any applicable law, the invalidity of that term does not affect the validity of any other term of this Agreement, and it should be deemed that the invalid term has been modified as much as possible to make it valid and enforceable, or if the term cannot be modified, it should be deemed to have been deleted from this Agreement. -8.4 The arbitration award is final, binding on both parties, and can be enforced in any court with jurisdiction. +10.4 The arbitration award is final, binding on both parties, and can be enforced in any court with jurisdiction. diff --git a/README.zh-CN.md b/README.zh-CN.md index b75e53ebc4..03af8e9e6e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -7,9 +7,6 @@ https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd1 NocoBase - Scalability-first, open-source no-code platform | Product Hunt -## 加入我们 -我们正在招聘远程 **全栈开发工程师** 、 **测试工程师** 、 **技术培训与文档专家**。 欢迎对 NocoBase 有强烈兴趣的伙伴加入。[查看详情](https://www.nocobase.com/cn/recruitment) - ## 最近重要更新 - [v1.3:REST API 数据源、移动端 V2 和更多功能 - 2024/08/29](https://www.nocobase.com/cn/blog/nocobase-1-3) - [v1.0.1-alpha.1:区块支持高度设置 - 2024/06/07](https://www.nocobase.com/cn/blog/release-v101-alpha1) @@ -39,7 +36,7 @@ https://demo-cn.nocobase.com/new 文档: https://docs-cn.nocobase.com/ -社区: +社区: https://forum.nocobase.com/ ## 与众不同之处 @@ -78,3 +75,9 @@ NocoBase 支持三种安装方式: - Git 源码安装 如果你想体验最新未发布版本,或者想参与贡献,需要在源码上进行修改、调试,建议选择这种安装方式,对开发技术水平要求较高,如果代码有更新,可以走 git 流程拉取最新代码。 + +## 一键部署 + +通过云厂商一键部署 NocoBase,并享受多种部署选项的灵活性: + +- [阿里云](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=NocoBase%20%E7%A4%BE%E5%8C%BA%E7%89%88) diff --git a/docker-compose.yml b/docker-compose.yml index 37009aeb9e..7cb8857692 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,24 @@ services: - "${DB_MYSQL_PORT}:3306" networks: - nocobase + kingbase: + image: registry.cn-shanghai.aliyuncs.com/nocobase/kingbase:v009r001c001b0030_single_x86 + platform: linux/amd64 + restart: always + privileged: true + networks: + - nocobase + ports: + - "${DB_KINGBASE_PORT}:54321" + environment: + ENABLE_CI: no + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + DB_MODE: pg + NEED_START: yes + command: ["/usr/sbin/init"] + volumes: + - ./storage/db/kingbase:/home/kingbase/userdata postgres: image: postgres:latest restart: always diff --git a/docker/nocobase/Dockerfile b/docker/nocobase/Dockerfile index db96d07fa9..3c78b4ba8e 100644 --- a/docker/nocobase/Dockerfile +++ b/docker/nocobase/Dockerfile @@ -27,7 +27,7 @@ RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \ esac \ && set -ex \ # libatomic1 for arm - && apt-get update && apt-get install -y nginx + && apt-get update && apt-get install -y nginx libaio1 RUN rm -rf /etc/nginx/sites-enabled/default COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz diff --git a/docker/nocobase/Dockerfile.next b/docker/nocobase/Dockerfile.next new file mode 100644 index 0000000000..6f13b09dad --- /dev/null +++ b/docker/nocobase/Dockerfile.next @@ -0,0 +1,43 @@ +FROM node:18-bullseye-slim as builder + +WORKDIR /app + +RUN cd /app \ + && yarn config set network-timeout 600000 -g \ + && npx -y create-nocobase-app@next my-nocobase-app -a -e APP_ENV=production \ + && cd /app/my-nocobase-app \ + && yarn install --production + +RUN cd /app \ + && rm -rf nocobase.tar.gz \ + && tar -zcf ./nocobase.tar.gz -C /app/my-nocobase-app . + +FROM node:18-bullseye-slim + +# COPY ./sources.list /etc/apt/sources.list +RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \ + && case "${dpkgArch##*-}" in \ + amd64) ARCH='x64';; \ + ppc64el) ARCH='ppc64le';; \ + s390x) ARCH='s390x';; \ + arm64) ARCH='arm64';; \ + armhf) ARCH='armv7l';; \ + i386) ARCH='x86';; \ + *) echo "unsupported architecture"; exit 1 ;; \ + esac \ + && set -ex \ + # libatomic1 for arm + && apt-get update && apt-get install -y nginx libaio1 + +RUN rm -rf /etc/nginx/sites-enabled/default +COPY --from=builder /app/nocobase.tar.gz /app/nocobase.tar.gz + +WORKDIR /app/nocobase + +COPY docker-entrypoint.sh /app/ +# COPY docker-entrypoint.sh /usr/local/bin/ +# ENTRYPOINT ["docker-entrypoint.sh"] + +EXPOSE 80/tcp + +CMD ["/app/docker-entrypoint.sh"] diff --git a/generate-npmignore.sh b/generate-npmignore.sh index 9b608df726..b669b9ca49 100755 --- a/generate-npmignore.sh +++ b/generate-npmignore.sh @@ -11,7 +11,8 @@ else CONTENT="/node_modules /docker /docs -/src" +/src +/dist/node_modules/external-db-data-source/src" fi echo $CONTENT diff --git a/lerna.json b/lerna.json index 9710afb03d..5496e73585 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "npmClient": "yarn", "useWorkspaces": true, "npmClientArgs": ["--ignore-engines"], diff --git a/packages/core/acl/README.md b/packages/core/acl/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/acl/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/acl/package.json b/packages/core/acl/package.json index 607220f2a3..81de4e4be8 100644 --- a/packages/core/acl/package.json +++ b/packages/core/acl/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/acl", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/resourcer": "1.4.0-alpha", - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/resourcer": "1.4.0-alpha.11", + "@nocobase/utils": "1.4.0-alpha.11", "minimatch": "^5.1.1" }, "repository": { diff --git a/packages/core/actions/README.md b/packages/core/actions/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/actions/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/actions/package.json b/packages/core/actions/package.json index bd259c9bb2..3958340c35 100644 --- a/packages/core/actions/package.json +++ b/packages/core/actions/package.json @@ -1,14 +1,14 @@ { "name": "@nocobase/actions", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/cache": "1.4.0-alpha", - "@nocobase/database": "1.4.0-alpha", - "@nocobase/resourcer": "1.4.0-alpha" + "@nocobase/cache": "1.4.0-alpha.11", + "@nocobase/database": "1.4.0-alpha.11", + "@nocobase/resourcer": "1.4.0-alpha.11" }, "repository": { "type": "git", diff --git a/packages/core/app/README.md b/packages/core/app/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/app/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/app/client/.umirc.ts b/packages/core/app/client/.umirc.ts index e070635481..1eceaf186f 100644 --- a/packages/core/app/client/.umirc.ts +++ b/packages/core/app/client/.umirc.ts @@ -18,24 +18,26 @@ export default defineConfig({ devtool: process.env.NODE_ENV === 'development' ? 'source-map' : false, favicons: [`${appPublicPath}favicon/favicon.ico`], metas: [{ name: 'viewport', content: 'initial-scale=0.1' }], - links: [ - { rel: 'stylesheet', href: `${appPublicPath}global.css` }, - ], + links: [{ rel: 'stylesheet', href: `${appPublicPath}global.css` }], headScripts: [ { src: `${appPublicPath}browser-checker.js`, }, { - content: isDevCmd ? '' : ` + content: isDevCmd + ? '' + : ` window['__webpack_public_path__'] = '{{env.APP_PUBLIC_PATH}}'; window['__nocobase_public_path__'] = '{{env.APP_PUBLIC_PATH}}'; window['__nocobase_api_base_url__'] = '{{env.API_BASE_URL}}'; window['__nocobase_api_client_storage_prefix__'] = '{{env.API_CLIENT_STORAGE_PREFIX}}'; + window['__nocobase_api_client_storage_type__'] = '{{env.API_CLIENT_STORAGE_TYPE}}'; window['__nocobase_ws_url__'] = '{{env.WS_URL}}'; window['__nocobase_ws_path__'] = '{{env.WS_PATH}}'; `, }, ], + cacheDirectoryPath: process.env.APP_CLIENT_CACHE_DIR || `node_modules/.cache`, outputPath: path.resolve(__dirname, '../dist/client'), hash: true, alias: { @@ -62,8 +64,11 @@ export default defineConfig({ edge: 79, safari: 12, }, + jsMinifierOptions: { + target: ['chrome80', 'es2020'], + }, codeSplitting: { - jsStrategy: 'depPerChunk' + jsStrategy: 'depPerChunk', }, chainWebpack(config, { env }) { if (env === 'production') { diff --git a/packages/core/app/client/src/pages/index.tsx b/packages/core/app/client/src/pages/index.tsx index 2b7b273228..2a27f6d896 100644 --- a/packages/core/app/client/src/pages/index.tsx +++ b/packages/core/app/client/src/pages/index.tsx @@ -13,6 +13,9 @@ import devDynamicImport from '../.plugins/index'; export const app = new Application({ apiClient: { + storageType: + // @ts-ignore + window['__nocobase_api_client_storage_type__'] || process.env.API_CLIENT_STORAGE_TYPE || 'localStorage', // @ts-ignore storagePrefix: // @ts-ignore diff --git a/packages/core/app/package.json b/packages/core/app/package.json index 8efe8f61f2..0a7b1b341f 100644 --- a/packages/core/app/package.json +++ b/packages/core/app/package.json @@ -1,17 +1,17 @@ { "name": "@nocobase/app", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/database": "1.4.0-alpha", - "@nocobase/preset-nocobase": "1.4.0-alpha", - "@nocobase/server": "1.4.0-alpha" + "@nocobase/database": "1.4.0-alpha.11", + "@nocobase/preset-nocobase": "1.4.0-alpha.11", + "@nocobase/server": "1.4.0-alpha.11" }, "devDependencies": { - "@nocobase/client": "1.4.0-alpha" + "@nocobase/client": "1.4.0-alpha.11" }, "repository": { "type": "git", diff --git a/packages/core/app/src/index.ts b/packages/core/app/src/index.ts index 8a775f5b6f..69f11f9d06 100644 --- a/packages/core/app/src/index.ts +++ b/packages/core/app/src/index.ts @@ -7,15 +7,18 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Gateway } from '@nocobase/server'; +import { Gateway, runPluginStaticImports } from '@nocobase/server'; import { getConfig } from './config'; -getConfig() - .then((config) => { - return Gateway.getInstance().run({ - mainAppOptions: config, - }); - }) - .catch((e) => { - // console.error(e); +async function initializeGateway() { + await runPluginStaticImports(); + const config = await getConfig(); + await Gateway.getInstance().run({ + mainAppOptions: config, }); +} + +initializeGateway().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/packages/core/auth/README.md b/packages/core/auth/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/auth/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/auth/package.json b/packages/core/auth/package.json index af0e356596..b47d1fd82c 100644 --- a/packages/core/auth/package.json +++ b/packages/core/auth/package.json @@ -1,16 +1,16 @@ { "name": "@nocobase/auth", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/actions": "1.4.0-alpha", - "@nocobase/cache": "1.4.0-alpha", - "@nocobase/database": "1.4.0-alpha", - "@nocobase/resourcer": "1.4.0-alpha", - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/actions": "1.4.0-alpha.11", + "@nocobase/cache": "1.4.0-alpha.11", + "@nocobase/database": "1.4.0-alpha.11", + "@nocobase/resourcer": "1.4.0-alpha.11", + "@nocobase/utils": "1.4.0-alpha.11", "@types/jsonwebtoken": "^8.5.8", "jsonwebtoken": "^8.5.1" }, diff --git a/packages/core/auth/src/auth-manager.ts b/packages/core/auth/src/auth-manager.ts index f0c625a1e5..40b53094ec 100644 --- a/packages/core/auth/src/auth-manager.ts +++ b/packages/core/auth/src/auth-manager.ts @@ -106,7 +106,9 @@ export class AuthManager { * @description Auth middleware, used to check the authentication status. */ middleware() { - return async (ctx: Context & { auth: Auth }, next: Next) => { + const self = this; + + return async function AuthManagerMiddleware(ctx: Context & { auth: Auth }, next: Next) { const token = ctx.getBearerToken(); if (token && (await ctx.app.authManager.jwt.blacklist?.has(token))) { return ctx.throw(401, { @@ -115,7 +117,8 @@ export class AuthManager { }); } - const name = ctx.get(this.options.authKey) || this.options.default; + const name = ctx.get(self.options.authKey) || self.options.default; + let authenticator: Auth; try { authenticator = await ctx.app.authManager.get(name, ctx); diff --git a/packages/core/build/README.md b/packages/core/build/README.md old mode 100755 new mode 100644 index e69de29bb2..d3e8120f6f --- a/packages/core/build/README.md +++ b/packages/core/build/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/build/package.json b/packages/core/build/package.json index 504c23010c..c60f31eef9 100644 --- a/packages/core/build/package.json +++ b/packages/core/build/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/build", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "Library build tool based on rollup.", "main": "lib/index.js", "types": "./lib/index.d.ts", @@ -14,16 +14,25 @@ "@babel/preset-env": "7.25.4", "@hapi/topo": "^6.0.0", "@lerna/project": "4.0.0", + "@rspack/core": "1.0.14", + "@svgr/webpack": "^8.1.0", "@types/gulp": "^4.0.13", "@types/lerna__package": "5.1.0", "@types/lerna__project": "5.1.0", "@types/tar": "^6.1.5", "@vercel/ncc": "0.36.1", "chalk": "2.4.2", + "css-loader": "^6.8.1", "esbuild-register": "^3.4.2", "fast-glob": "^3.3.1", "gulp": "4.0.2", "gulp-typescript": "6.0.0-alpha.1", + "less": "^4.1.3", + "less-loader": "11.1.0", + "postcss": "^8.4.29", + "postcss-loader": "^7.3.3", + "postcss-preset-env": "^9.1.2", + "style-loader": "^3.3.3", "tar": "^6.2.0", "tsup": "8.2.4", "typescript": "5.1.3", diff --git a/packages/core/build/src/buildClient.ts b/packages/core/build/src/buildClient.ts index 19101af77a..eb732d0793 100644 --- a/packages/core/build/src/buildClient.ts +++ b/packages/core/build/src/buildClient.ts @@ -17,6 +17,7 @@ import { libInjectCss } from 'vite-plugin-lib-inject-css'; import { globExcludeFiles } from './constant'; import { PkgLog, UserConfig, getEnvDefine } from './utils'; +import { rspack } from '@rspack/core'; export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap: boolean = false, log: PkgLog) { log('build client'); @@ -29,7 +30,7 @@ export async function buildClient(cwd: string, userConfig: UserConfig, sourcemap return true; }; await buildClientEsm(cwd, userConfig, sourcemap, external, log); - await buildClientLib(cwd, userConfig, sourcemap, external, log); + // await buildClientLib(cwd, userConfig, sourcemap, external, log); await buildLocale(cwd, userConfig, log); } @@ -39,31 +40,170 @@ function buildClientEsm(cwd: string, userConfig: UserConfig, sourcemap: boolean, log('build client esm'); const entry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/'); const outDir = path.resolve(cwd, 'es'); - return viteBuild( - userConfig.modifyViteConfig({ - mode: process.env.NODE_ENV || 'production', - define: getEnvDefine(), - build: { - minify: process.env.NODE_ENV === 'production', - outDir, - cssCodeSplit: true, - emptyOutDir: true, - sourcemap, - lib: { - entry, - formats: ['es'], - fileName: 'index', - }, - target: ['es2015', 'edge88', 'firefox78', 'chrome87', 'safari14'], - rollupOptions: { - cache: true, - treeshake: true, - external, - }, + + return rspack({ + entry: { + index: entry, + }, + output: { + path: outDir, + library: { + type: 'module', }, - plugins: [react(), libInjectCss()], - }), - ); + clean: true, + }, + target: ['es2015', 'web'], + mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', + optimization: { + minimize: process.env.NODE_ENV === 'production', + moduleIds: 'deterministic', + sideEffects: true, + }, + resolve: { + tsConfig: path.join(process.cwd(), 'tsconfig.json'), + extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'], + }, + module: { + rules: [ + { + test: /\.less$/, + use: [ + { loader: 'style-loader' }, + { loader: 'css-loader' }, + { loader: require.resolve('less-loader') }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-preset-env': { + browsers: ['last 2 versions', '> 1%', 'cover 99.5%', 'not dead'], + }, + autoprefixer: {}, + }, + }, + }, + }, + ], + type: 'javascript/auto', + }, + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader', + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-preset-env': { + browsers: ['last 2 versions', '> 1%', 'cover 99.5%', 'not dead'], + }, + autoprefixer: {}, + }, + }, + }, + }, + ], + type: 'javascript/auto', + }, + { + test: /\.(png|jpe?g|gif)$/i, + type: 'asset', + }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }, + { + test: /\.jsx$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'ecmascript', + jsx: true, + }, + target: 'es5', + }, + }, + }, + { + test: /\.tsx$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + target: 'es5', + }, + }, + }, + { + test: /\.ts$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'typescript', + }, + target: 'es5', + }, + }, + }, + ], + }, + externals: [ + function ({ request }, callback) { + if (external(request)) { + return callback(null, true); + } + callback(); + } + ], + plugins: [ + new rspack.DefinePlugin(getEnvDefine()), + ], + stats: 'errors-warnings', + }); + + // const entry = path.join(cwd, 'src/index.ts').replaceAll(/\\/g, '/'); + // const outDir = path.resolve(cwd, 'es'); + // return viteBuild( + // userConfig.modifyViteConfig({ + // mode: process.env.NODE_ENV || 'production', + // define: getEnvDefine(), + // build: { + // minify: process.env.NODE_ENV === 'production', + // outDir, + // cssCodeSplit: true, + // emptyOutDir: true, + // sourcemap, + // lib: { + // entry, + // formats: ['es'], + // fileName: 'index', + // }, + // target: ['es2015', 'edge88', 'firefox78', 'chrome87', 'safari14'], + // rollupOptions: { + // cache: true, + // treeshake: true, + // external, + // }, + // }, + // plugins: [react(), libInjectCss()], + // }), + // ); } async function buildClientLib( diff --git a/packages/core/build/src/buildEsm.ts b/packages/core/build/src/buildEsm.ts index e0751a6888..4a773c7066 100644 --- a/packages/core/build/src/buildEsm.ts +++ b/packages/core/build/src/buildEsm.ts @@ -7,11 +7,11 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ - import path from 'path'; import { PkgLog, UserConfig, getEnvDefine } from './utils'; import { build as viteBuild } from 'vite'; import fg from 'fast-glob'; +import { rspack } from '@rspack/core'; const clientExt = '.{ts,tsx,js,jsx}'; @@ -45,7 +45,14 @@ export async function buildEsm(cwd: string, userConfig: UserConfig, sourcemap: b } } -function build(cwd: string, entry: string, outDir: string, userConfig: UserConfig, sourcemap: boolean = false, log: PkgLog) { +function build( + cwd: string, + entry: string, + outDir: string, + userConfig: UserConfig, + sourcemap: boolean = false, + log: PkgLog, +) { const cwdWin = cwd.replaceAll(/\\/g, '/'); const cwdUnix = cwd.replaceAll(/\//g, '\\'); const external = function (id: string) { @@ -54,28 +61,158 @@ function build(cwd: string, entry: string, outDir: string, userConfig: UserConfi } return true; }; - return viteBuild( - userConfig.modifyViteConfig({ - mode: process.env.NODE_ENV || 'production', - define: getEnvDefine(), - build: { - minify: false, - outDir, - cssCodeSplit: true, - emptyOutDir: true, - sourcemap, - lib: { - entry, - formats: ['es'], - fileName: 'index', - }, - target: ['node16'], - rollupOptions: { - cache: true, - treeshake: true, - external, - }, + + return rspack({ + entry: { + index: entry, + }, + output: { + path: outDir, + library: { + type: 'module', }, - }), - ); + clean: true, + }, + target: ['node16'], + mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', + resolve: { + tsConfig: path.join(process.cwd(), 'tsconfig.json'), + extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'], + }, + module: { + rules: [ + { + test: /\.less$/, + use: [ + { loader: 'style-loader' }, + { loader: 'css-loader' }, + { loader: require.resolve('less-loader') }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-preset-env': { + browsers: ['last 2 versions', '> 1%', 'cover 99.5%', 'not dead'], + }, + autoprefixer: {}, + }, + }, + }, + }, + ], + type: 'javascript/auto', + }, + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader', + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-preset-env': { + browsers: ['last 2 versions', '> 1%', 'cover 99.5%', 'not dead'], + }, + autoprefixer: {}, + }, + }, + }, + }, + ], + type: 'javascript/auto', + }, + { + test: /\.(png|jpe?g|gif)$/i, + type: 'asset', + }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }, + { + test: /\.jsx$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'ecmascript', + jsx: true, + }, + target: 'es5', + }, + }, + }, + { + test: /\.tsx$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + target: 'es5', + }, + }, + }, + { + test: /\.ts$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'typescript', + }, + target: 'es5', + }, + }, + }, + ], + }, + externals: [ + function ({ request }, callback) { + if (external(request)) { + return callback(null, true); + } + callback(); + }, + ], + plugins: [new rspack.DefinePlugin(getEnvDefine())], + stats: 'errors-warnings', + }); + + // return viteBuild( + // userConfig.modifyViteConfig({ + // mode: process.env.NODE_ENV || 'production', + // define: getEnvDefine(), + // build: { + // minify: false, + // outDir, + // cssCodeSplit: true, + // emptyOutDir: true, + // sourcemap, + // lib: { + // entry, + // formats: ['es'], + // fileName: 'index', + // }, + // target: ['node16'], + // rollupOptions: { + // cache: true, + // treeshake: true, + // external, + // }, + // }, + // }), + // ); } diff --git a/packages/core/build/src/buildPlugin.ts b/packages/core/build/src/buildPlugin.ts index 20b01bcd46..5c6a912bdd 100644 --- a/packages/core/build/src/buildPlugin.ts +++ b/packages/core/build/src/buildPlugin.ts @@ -16,9 +16,10 @@ import path from 'path'; import { build as tsupBuild } from 'tsup'; import { build as viteBuild } from 'vite'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; +import { rspack } from '@rspack/core'; import { EsbuildSupportExts, globExcludeFiles } from './constant'; -import { PkgLog, UserConfig, getEnvDefine, getPackageJson } from './utils'; +import { PkgLog, UserConfig, getPackageJson } from './utils'; import { buildCheck, checkFileSize, @@ -130,6 +131,7 @@ const external = [ 'ahooks', 'lodash', 'china-division', + 'file-saver', ]; const pluginPrefix = ( process.env.PLUGIN_PACKAGE_PREFIX || '@nocobase/plugin-,@nocobase/preset-,@nocobase/plugin-pro-' @@ -158,7 +160,9 @@ export function deleteServerFiles(cwd: string, log: PkgLog) { export function writeExternalPackageVersion(cwd: string, log: PkgLog) { log('write external version'); - const sourceFiles = fg.globSync(sourceGlobalFiles, { cwd, absolute: true }).map((item) => fs.readFileSync(item, 'utf-8')); + const sourceFiles = fg + .globSync(sourceGlobalFiles, { cwd, absolute: true }) + .map((item) => fs.readFileSync(item, 'utf-8')); const sourcePackages = getSourcePackages(sourceFiles); const excludePackages = getExcludePackages(sourcePackages, external, pluginPrefix); const data = excludePackages.reduce>((prev, packageName) => { @@ -174,7 +178,9 @@ export function writeExternalPackageVersion(cwd: string, log: PkgLog) { export async function buildServerDeps(cwd: string, serverFiles: string[], log: PkgLog) { log('build plugin server dependencies'); const outDir = path.join(cwd, target_dir, 'node_modules'); - const serverFileSource = serverFiles.filter(item => validExts.includes(path.extname(item))).map((item) => fs.readFileSync(item, 'utf-8')); + const serverFileSource = serverFiles + .filter((item) => validExts.includes(path.extname(item))) + .map((item) => fs.readFileSync(item, 'utf-8')); const sourcePackages = getSourcePackages(serverFileSource); const includePackages = getIncludePackages(sourcePackages, external, pluginPrefix); const excludePackages = getExcludePackages(sourcePackages, external, pluginPrefix); @@ -190,7 +196,9 @@ export async function buildServerDeps(cwd: string, serverFiles: string[], log: P if (excludePackages.length) { tips.push(`These packages ${chalk.yellow(excludePackages.join(', '))} will be ${chalk.italic('exclude')}.`); } - tips.push(`For more information, please refer to: ${chalk.blue('https://docs.nocobase.com/development/deps')}.`); + tips.push( + `For more information, please refer to: ${chalk.blue('https://docs.nocobase.com/development/others/deps')}.`, + ); log(tips.join(' ')); if (!includePackages.length) return; @@ -268,30 +276,34 @@ export async function buildPluginServer(cwd: string, userConfig: UserConfig, sou const packageJson = getPackageJson(cwd); const serverFiles = fg.globSync(serverGlobalFiles, { cwd, absolute: true }); buildCheck({ cwd, packageJson, entry: 'server', files: serverFiles, log }); - const otherExts = Array.from(new Set(serverFiles.map((item) => path.extname(item)).filter((item) => !EsbuildSupportExts.includes(item)))); + const otherExts = Array.from( + new Set(serverFiles.map((item) => path.extname(item)).filter((item) => !EsbuildSupportExts.includes(item))), + ); if (otherExts.length) { log('%s will not be processed, only be copied to the dist directory.', chalk.yellow(otherExts.join(','))); } deleteServerFiles(cwd, log); - await tsupBuild(userConfig.modifyTsupConfig({ - entry: serverFiles, - splitting: false, - clean: false, - bundle: false, - silent: true, - treeshake: false, - target: 'node16', - sourcemap, - outDir: path.join(cwd, target_dir), - format: 'cjs', - skipNodeModulesBundle: true, - loader: { - ...otherExts.reduce((prev, cur) => ({ ...prev, [cur]: 'copy' }), {}), - '.json': 'copy', - }, - })); + await tsupBuild( + userConfig.modifyTsupConfig({ + entry: serverFiles, + splitting: false, + clean: false, + bundle: false, + silent: true, + treeshake: false, + target: 'node16', + sourcemap, + outDir: path.join(cwd, target_dir), + format: 'cjs', + skipNodeModulesBundle: true, + loader: { + ...otherExts.reduce((prev, cur) => ({ ...prev, [cur]: 'copy' }), {}), + '.json': 'copy', + }, + }), + ); await buildServerDeps(cwd, serverFiles, log); } @@ -316,46 +328,199 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou return prev; }, {}); - const entry = fg.globSync('src/client/index.{ts,tsx,js,jsx}', { absolute: true, cwd }); + const entry = fg.globSync('index.{ts,tsx,js,jsx}', { absolute: false, cwd: path.join(cwd, 'src/client') }); const outputFileName = 'index.js'; - - await viteBuild(userConfig.modifyViteConfig({ - mode: process.env.NODE_ENV || 'production', - define: getEnvDefine(), - logLevel: 'warn', - build: { - minify: process.env.NODE_ENV === 'production', - outDir, - cssCodeSplit: false, - emptyOutDir: true, - sourcemap, - lib: { - entry, - formats: ['umd'], + const compiler = rspack({ + mode: 'production', + // mode: "development", + context: cwd, + entry: './src/client/' + entry[0], + target: ['web', 'es5'], + output: { + path: outDir, + filename: outputFileName, + publicPath: `/static/plugins/${packageJson.name}/dist/client/`, + clean: true, + library: { name: packageJson.name, - fileName: () => outputFileName, - }, - target: ['es2015', 'edge88', 'firefox78', 'chrome87', 'safari14'], - rollupOptions: { - cache: true, - external: [...Object.keys(globals), 'react', 'react/jsx-runtime'], - output: { - exports: 'named', - globals: { - react: 'React', - 'react/jsx-runtime': 'jsxRuntime', - ...globals, - }, - }, + type: 'umd', + umdNamedDefine: true, }, }, + resolve: { + tsConfig: path.join(process.cwd(), 'tsconfig.json'), + extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'], + }, + module: { + rules: [ + { + test: /\.less$/, + use: [ + { loader: 'style-loader' }, + { loader: 'css-loader' }, + { loader: require.resolve('less-loader') }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-preset-env': { + browsers: ['last 2 versions', '> 1%', 'cover 99.5%', 'not dead'], + }, + autoprefixer: {}, + }, + }, + }, + }, + ], + type: 'javascript/auto', + }, + { + test: /\.css$/, + use: [ + 'style-loader', + 'css-loader', + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: { + 'postcss-preset-env': { + browsers: ['last 2 versions', '> 1%', 'cover 99.5%', 'not dead'], + }, + autoprefixer: {}, + }, + }, + }, + }, + ], + type: 'javascript/auto', + }, + { + test: /\.(png|jpe?g|gif)$/i, + type: 'asset', + }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + use: ['@svgr/webpack'], + }, + { + test: /\.jsx$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'ecmascript', + jsx: true, + }, + target: 'es5', + }, + }, + }, + { + test: /\.tsx$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + target: 'es5', + }, + }, + }, + { + test: /\.ts$/, + exclude: /[\\/]node_modules[\\/]/, + loader: 'builtin:swc-loader', + options: { + sourceMap: true, + jsc: { + parser: { + syntax: 'typescript', + }, + target: 'es5', + }, + }, + }, + ], + }, plugins: [ - react(), - cssInjectedByJsPlugin({ styleId: packageJson.name }), + new rspack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production'), + }), ], - })); + node: { + global: true, + }, + externals: { + react: 'React', + lodash: 'lodash', + // 'react/jsx-runtime': 'jsxRuntime', + ...globals, + }, + stats: 'errors-warnings', + }); - checkFileSize(outDir, log); + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + const compilationErrors = stats?.compilation.errors; + const infos = stats.toString({ + colors: true, + }); + if (err || compilationErrors?.length) { + reject(err || infos); + return; + } + console.log(infos); + resolve(null); + }); + }); + // await viteBuild(userConfig.modifyViteConfig({ + // mode: 'production', + // define: { + // 'process.env.NODE_ENV': JSON.stringify('production'), + // }, + // logLevel: 'warn', + // build: { + // minify: true, + // outDir, + // cssCodeSplit: false, + // emptyOutDir: true, + // sourcemap, + // lib: { + // entry, + // formats: ['umd'], + // name: packageJson.name, + // fileName: () => outputFileName, + // }, + // target: ['es2015', 'edge88', 'firefox78', 'chrome87', 'safari14'], + // rollupOptions: { + // cache: true, + // external: [...Object.keys(globals), 'react', 'react/jsx-runtime'], + // output: { + // exports: 'named', + // globals: { + // react: 'React', + // 'react/jsx-runtime': 'jsxRuntime', + // ...globals, + // }, + // }, + // }, + // }, + // plugins: [ + // react(), + // cssInjectedByJsPlugin({ styleId: packageJson.name }), + // ], + // })); + + // checkFileSize(outDir, log); } export async function buildPlugin(cwd: string, userConfig: UserConfig, sourcemap: boolean, log: PkgLog) { diff --git a/packages/core/build/src/utils/buildPluginUtils.ts b/packages/core/build/src/utils/buildPluginUtils.ts index 7c71418d9c..e36be7eb5c 100644 --- a/packages/core/build/src/utils/buildPluginUtils.ts +++ b/packages/core/build/src/utils/buildPluginUtils.ts @@ -112,7 +112,7 @@ export function checkDependencies(packageJson: Record, log: Log) { chalk.yellow(packages.join(', ')), chalk.yellow('dependencies'), chalk.yellow('devDependencies'), - chalk.blue(chalk.blue('https://docs.nocobase.com/development/deps')), + chalk.blue(chalk.blue('https://docs.nocobase.com/development/others/deps')), ); } diff --git a/packages/core/cache/README.md b/packages/core/cache/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/cache/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/cache/package.json b/packages/core/cache/package.json index 1350f24464..60f44f03ab 100644 --- a/packages/core/cache/package.json +++ b/packages/core/cache/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/cache", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", diff --git a/packages/core/cache/src/cache-manager.ts b/packages/core/cache/src/cache-manager.ts index e39545b10e..79ff749245 100644 --- a/packages/core/cache/src/cache-manager.ts +++ b/packages/core/cache/src/cache-manager.ts @@ -100,7 +100,7 @@ export class CacheManager { async createCache(options: { name: string; prefix?: string; store?: string; [key: string]: any }) { const { name, prefix, store = this.defaultStore, ...config } = options; - if (!lodash.isEmpty(config)) { + if (!lodash.isEmpty(config) || store === 'memory') { const newStore = await this.createStore({ name, storeType: store, ...config }); return this.newCache({ name, prefix, store: newStore }); } diff --git a/packages/core/cli/README.md b/packages/core/cli/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/cli/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/cli/bin/index.js b/packages/core/cli/bin/index.js index f8324deaa2..20df29c0cb 100755 --- a/packages/core/cli/bin/index.js +++ b/packages/core/cli/bin/index.js @@ -11,6 +11,14 @@ if (require('semver').satisfies(process.version, '<16')) { process.exit(1); } +if (__dirname.includes(' ')) { + console.error(chalk.red(`[nocobase cli]: PathError: Invalid path "${process.cwd()}"`)); + console.error( + chalk.red('[nocobase cli]: PathError: The path cannot contain spaces. Please modify the path and try again.'), + ); + process.exit(1); +} + // if (require('semver').satisfies(process.version, '>16') && !process.env.UNSET_NODE_OPTIONS) { // if (process.env.NODE_OPTIONS) { // let opts = process.env.NODE_OPTIONS; diff --git a/packages/core/cli/package.json b/packages/core/cli/package.json index b04f528041..d1b17045de 100644 --- a/packages/core/cli/package.json +++ b/packages/core/cli/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/cli", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./src/index.js", @@ -8,7 +8,7 @@ "nocobase": "./bin/index.js" }, "dependencies": { - "@nocobase/app": "1.4.0-alpha", + "@nocobase/app": "1.4.0-alpha.11", "@types/fs-extra": "^11.0.1", "@umijs/utils": "3.5.20", "chalk": "^4.1.1", @@ -25,7 +25,7 @@ "tsx": "^4.19.0" }, "devDependencies": { - "@nocobase/devtools": "1.4.0-alpha" + "@nocobase/devtools": "1.4.0-alpha.11" }, "repository": { "type": "git", diff --git a/packages/core/cli/src/commands/dev.js b/packages/core/cli/src/commands/dev.js index 8a805adb60..5eebb59003 100644 --- a/packages/core/cli/src/commands/dev.js +++ b/packages/core/cli/src/commands/dev.js @@ -38,16 +38,16 @@ module.exports = (cli) => { depth: 1, // 只监听第一层目录 }); + await fs.promises.mkdir(path.dirname(process.env.WATCH_FILE), { recursive: true }); + watcher .on('addDir', async (pathname) => { generatePlugins(); - const file = path.resolve(process.cwd(), 'storage/app.watch.ts'); - await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8'); + await fs.promises.writeFile(process.env.WATCH_FILE, `export const watchId = '${uid()}';`, 'utf-8'); }) .on('unlinkDir', async (pathname) => { generatePlugins(); - const file = path.resolve(process.cwd(), 'storage/app.watch.ts'); - await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8'); + await fs.promises.writeFile(process.env.WATCH_FILE, `export const watchId = '${uid()}';`, 'utf-8'); }); promptForTs(); diff --git a/packages/core/cli/src/commands/postinstall.js b/packages/core/cli/src/commands/postinstall.js index cbe7bf48e6..202fb6656c 100644 --- a/packages/core/cli/src/commands/postinstall.js +++ b/packages/core/cli/src/commands/postinstall.js @@ -18,9 +18,16 @@ function writeToExclude() { const excludePath = resolve(process.cwd(), '.git', 'info', 'exclude'); const content = 'packages/pro-plugins/\n'; const dirPath = dirname(excludePath); + if (!existsSync(dirPath)) { - mkdirSync(dirPath, { recursive: true }); + try { + mkdirSync(dirPath, { recursive: true }); + } catch (e) { + console.log(`${e.message}, ignore write to git exclude`); + return; + } } + let fileContent = ''; if (existsSync(excludePath)) { fileContent = readFileSync(excludePath, 'utf-8'); diff --git a/packages/core/cli/src/util.js b/packages/core/cli/src/util.js index 66c834b047..bbf2f73d56 100644 --- a/packages/core/cli/src/util.js +++ b/packages/core/cli/src/util.js @@ -291,6 +291,7 @@ function buildIndexHtml(force = false) { const data = fs.readFileSync(tpl, 'utf-8'); const replacedData = data .replace(/\{\{env.APP_PUBLIC_PATH\}\}/g, process.env.APP_PUBLIC_PATH) + .replace(/\{\{env.API_CLIENT_STORAGE_TYPE\}\}/g, process.env.API_CLIENT_STORAGE_TYPE) .replace(/\{\{env.API_CLIENT_STORAGE_PREFIX\}\}/g, process.env.API_CLIENT_STORAGE_PREFIX) .replace(/\{\{env.API_BASE_URL\}\}/g, process.env.API_BASE_URL || process.env.API_BASE_PATH) .replace(/\{\{env.WS_URL\}\}/g, process.env.WEBSOCKET_URL || '') @@ -327,6 +328,7 @@ exports.initEnv = function initEnv() { APP_PORT: 13000, API_BASE_PATH: '/api/', API_CLIENT_STORAGE_PREFIX: 'NOCOBASE_', + API_CLIENT_STORAGE_TYPE: 'localStorage', DB_DIALECT: 'sqlite', DB_STORAGE: 'storage/db/nocobase.sqlite', // DB_TIMEZONE: '+00:00', @@ -348,6 +350,7 @@ exports.initEnv = function initEnv() { LOGGER_BASE_PATH: 'storage/logs', APP_SERVER_BASE_URL: '', APP_PUBLIC_PATH: '/', + WATCH_FILE: resolve(process.cwd(), 'storage/app.watch.ts'), }; if ( diff --git a/packages/core/client/.dumirc.ts b/packages/core/client/.dumirc.ts index 5b10693cdd..1debccd479 100644 --- a/packages/core/client/.dumirc.ts +++ b/packages/core/client/.dumirc.ts @@ -26,6 +26,9 @@ export default defineConfig({ { type: 'component', dir: 'src/schema-component/antd' }, ], }, + jsMinifierOptions: { + target: ['chrome80', 'es2020'], + }, locales: lang === 'zh-CN' ? [{ id: 'zh-CN', name: '中文' },] : [{ id: 'en-US', name: 'English' }], themeConfig: defineThemeConfig({ title: 'NocoBase', diff --git a/packages/core/client/README.md b/packages/core/client/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/client/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 82f58fc9c0..fabd75e0f8 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/client", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "lib/index.js", "module": "es/index.mjs", @@ -11,7 +11,7 @@ "@ant-design/icons": "^5.1.4", "@ant-design/pro-layout": "^7.16.11", "@antv/g2plot": "^2.4.18", - "@budibase/handlebars-helpers": "^0.13.2", + "@budibase/handlebars-helpers": "^0.14.0", "@ctrl/tinycolor": "^3.6.0", "@dnd-kit/core": "^5.0.1", "@dnd-kit/modifiers": "^6.0.0", @@ -27,13 +27,13 @@ "@formily/reactive-react": "^2.2.27", "@formily/shared": "^2.2.27", "@formily/validator": "^2.2.27", - "@nocobase/evaluators": "1.4.0-alpha", - "@nocobase/sdk": "1.4.0-alpha", - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/evaluators": "1.4.0-alpha.11", + "@nocobase/sdk": "1.4.0-alpha.11", + "@nocobase/utils": "1.4.0-alpha.11", "ahooks": "^3.7.2", "antd": "5.12.8", - "antd-style": "3.4.5", - "axios": "^0.26.1", + "antd-style": "3.7.1", + "axios": "^1.7.0", "bignumber.js": "^9.1.2", "classnames": "^2.3.1", "cronstrue": "^2.11.0", diff --git a/packages/core/client/src/acl/ACLProvider.tsx b/packages/core/client/src/acl/ACLProvider.tsx index edba0767ba..4a95a03829 100644 --- a/packages/core/client/src/acl/ACLProvider.tsx +++ b/packages/core/client/src/acl/ACLProvider.tsx @@ -15,12 +15,11 @@ import { Navigate } from 'react-router-dom'; import { useAPIClient, useRequest } from '../api-client'; import { useAppSpin } from '../application/hooks/useAppSpin'; import { useBlockRequestContext } from '../block-provider/BlockProvider'; -import { useCollectionManager_deprecated, useCollection_deprecated } from '../collection-manager'; import { useResourceActionContext } from '../collection-manager/ResourceActionProvider'; +import { CollectionNotAllowViewPlaceholder, useCollection, useCollectionManager } from '../data-source'; import { useDataSourceKey } from '../data-source/data-source/DataSourceProvider'; import { useRecord } from '../record-provider'; import { SchemaComponentOptions, useDesignable } from '../schema-component'; -import { CollectionNotAllowViewPlaceholder } from '../data-source'; import { useApp } from '../application'; @@ -115,29 +114,41 @@ export const useACLRolesCheck = () => { const dataSourceName = useDataSourceKey(); const { dataSources: dataSourcesAcl } = ctx?.data?.meta || {}; const data = { ...ctx?.data?.data, ...omit(dataSourcesAcl?.[dataSourceName], 'snippets') }; - const getActionAlias = (actionPath: string) => { - const actionName = actionPath.split(':').pop(); - return data?.actionAlias?.[actionName] || actionName; - }; + const getActionAlias = useCallback( + (actionPath: string) => { + const actionName = actionPath.split(':').pop(); + return data?.actionAlias?.[actionName] || actionName; + }, + [data?.actionAlias], + ); return { data, getActionAlias, - inResources: (resourceName: string) => { - return data?.resources?.includes?.(resourceName); - }, - getResourceActionParams: (actionPath: string) => { - const [resourceName] = actionPath.split(':'); - const actionAlias = getActionAlias(actionPath); - return data?.actions?.[`${resourceName}:${actionAlias}`] || data?.actions?.[actionPath]; - }, - getStrategyActionParams: (actionPath: string) => { - const actionAlias = getActionAlias(actionPath); - const strategyAction = data?.strategy?.actions?.find((action) => { - const [value] = action.split(':'); - return value === actionAlias; - }); - return strategyAction ? {} : null; - }, + inResources: useCallback( + (resourceName: string) => { + return data?.resources?.includes?.(resourceName); + }, + [data?.resources], + ), + getResourceActionParams: useCallback( + (actionPath: string) => { + const [resourceName] = actionPath.split(':'); + const actionAlias = getActionAlias(actionPath); + return data?.actions?.[`${resourceName}:${actionAlias}`] || data?.actions?.[actionPath]; + }, + [data?.actions, getActionAlias], + ), + getStrategyActionParams: useCallback( + (actionPath: string) => { + const actionAlias = getActionAlias(actionPath); + const strategyAction = data?.strategy?.actions?.find((action) => { + const [value] = action.split(':'); + return value === actionAlias; + }); + return strategyAction ? {} : null; + }, + [data?.strategy?.actions, getActionAlias], + ), }; }; @@ -179,36 +190,43 @@ const useResourceName = () => { export function useACLRoleContext() { const { data, getActionAlias, inResources, getResourceActionParams, getStrategyActionParams } = useACLRolesCheck(); const allowedActions = useAllowedActions(); - const { getCollectionJoinField } = useCollectionManager_deprecated(); - const verifyScope = (actionName: string, recordPkValue: any) => { - const actionAlias = getActionAlias(actionName); - if (!Array.isArray(allowedActions?.[actionAlias])) { - return null; - } - return allowedActions[actionAlias].includes(recordPkValue); - }; + const cm = useCollectionManager(); + const verifyScope = useCallback( + (actionName: string, recordPkValue: any) => { + const actionAlias = getActionAlias(actionName); + if (!Array.isArray(allowedActions?.[actionAlias])) { + return null; + } + return allowedActions[actionAlias].includes(recordPkValue); + }, + [allowedActions, getActionAlias], + ); + return { ...data, - parseAction: (actionPath: string, options: any = {}) => { - const [resourceName, actionName] = actionPath.split(':'); - const targetResource = resourceName?.includes('.') && getCollectionJoinField(resourceName)?.target; - if (!getIgnoreScope(options)) { - const r = verifyScope(actionName, options.recordPkValue); - if (r !== null) { - return r ? {} : null; + parseAction: useCallback( + (actionPath: string, options: any = {}) => { + const [resourceName, actionName] = actionPath?.split(':') || []; + const targetResource = resourceName?.includes('.') && cm.getCollectionField(resourceName)?.target; + if (!getIgnoreScope(options)) { + const r = verifyScope(actionName, options.recordPkValue); + if (r !== null) { + return r ? {} : null; + } } - } - if (data?.allowAll) { - return {}; - } - if (inResources(targetResource)) { - return getResourceActionParams(`${targetResource}:${actionName}`); - } - if (inResources(resourceName)) { - return getResourceActionParams(actionPath); - } - return getStrategyActionParams(actionPath); - }, + if (data?.allowAll) { + return {}; + } + if (inResources(targetResource)) { + return getResourceActionParams(`${targetResource}:${actionName}`); + } + if (inResources(resourceName)) { + return getResourceActionParams(actionPath); + } + return getStrategyActionParams(actionPath); + }, + [cm, data?.allowAll, getResourceActionParams, getStrategyActionParams, inResources, verifyScope], + ), }; } @@ -228,19 +246,29 @@ export const ACLCollectionProvider = (props) => { const { allowAll: customAllowAll } = useACLCustomContext(); const app = useApp(); const schema = useFieldSchema(); - if (allowAll || app.disableAcl || customAllowAll) { - return props.children; - } + let actionPath = schema?.['x-acl-action'] || props.actionPath; const resoureName = schema?.['x-decorator-props']?.['association'] || schema?.['x-decorator-props']?.['collection']; + // 兼容 undefined 的情况 if (actionPath === 'undefined:list' && resoureName && resoureName !== 'undefined') { actionPath = `${resoureName}:list`; } + + const params = useMemo(() => { + if (!actionPath) { + return null; + } + return parseAction(actionPath, { schema }); + }, [parseAction, actionPath, schema]); + + if (allowAll || app.disableAcl || customAllowAll) { + return props.children; + } if (!actionPath) { return props.children; } - const params = parseAction(actionPath, { schema }); + if (!params) { return ; } @@ -254,39 +282,51 @@ export const useACLActionParamsContext = () => { }; export const useRecordPkValue = () => { - const { getPrimaryKey } = useCollection_deprecated(); + const collection = useCollection(); const record = useRecord(); - const primaryKey = getPrimaryKey(); + + if (!collection) { + return; + } + + const primaryKey = collection.getPrimaryKey(); return record?.[primaryKey]; }; export const ACLActionProvider = (props) => { - const { template, writableView } = useCollection_deprecated(); + const collection = useCollection(); const recordPkValue = useRecordPkValue(); const resource = useResourceName(); const { parseAction } = useACLRoleContext(); const schema = useFieldSchema(); let actionPath = schema['x-acl-action']; const editablePath = ['create', 'update', 'destroy', 'importXlsx']; + if (!actionPath && resource && schema['x-action']) { actionPath = `${resource}:${schema['x-action']}`; } if (!actionPath?.includes(':')) { actionPath = `${resource}:${actionPath}`; } + + const params = useMemo( + () => parseAction(actionPath, { schema, recordPkValue }), + [parseAction, actionPath, schema, recordPkValue], + ); + if (!actionPath) { return <>{props.children}; } if (!resource) { return <>{props.children}; } - const params = parseAction(actionPath, { schema, recordPkValue }); + if (!params) { return {props.children}; } //视图表无编辑权限时不显示 if (editablePath.includes(actionPath) || editablePath.includes(actionPath?.split(':')[1])) { - if (template !== 'view' || writableView) { + if ((collection && collection.template !== 'view') || collection?.writableView) { return {props.children}; } return null; @@ -305,7 +345,7 @@ export const useACLFieldWhitelist = () => { return { whitelist, schemaInWhitelist: useCallback( - (fieldSchema: Schema, isSkip?) => { + (fieldSchema: Schema | any, isSkip?) => { if (isSkip) { return true; } @@ -319,7 +359,8 @@ export const useACLFieldWhitelist = () => { return true; } const [key1, key2] = fieldSchema['x-collection-field'].split('.'); - return whitelist?.includes(key2 || key1); + const [associationField] = fieldSchema['name'].split('.'); + return whitelist?.includes(associationField || key2 || key1); }, [whitelist], ), diff --git a/packages/core/client/src/api-client/APIClient.ts b/packages/core/client/src/api-client/APIClient.ts index 9fa5b7e561..93777c64fb 100644 --- a/packages/core/client/src/api-client/APIClient.ts +++ b/packages/core/client/src/api-client/APIClient.ts @@ -61,6 +61,18 @@ export class APIClient extends APIClientSDK { /** 该值会在 AntdAppProvider 中被重新赋值 */ notification: any = notification; + cloneInstance() { + const api = new APIClient(this.options); + api.options = this.options; + api.services = this.services; + api.storage = this.storage; + api.app = this.app; + api.auth = this.auth; + api.storagePrefix = this.storagePrefix; + api.notification = this.notification; + return api; + } + getHeaders() { const headers = super.getHeaders(); const appName = this.app.getName(); @@ -180,7 +192,8 @@ export class APIClient extends APIClientSDK { } silent() { - this.silence = true; - return this; + const api = this.cloneInstance(); + api.silence = true; + return api; } } diff --git a/packages/core/client/src/application/__tests__/Application.test.tsx b/packages/core/client/src/application/__tests__/Application.test.tsx index 2db784f2d1..8fdcb59b94 100644 --- a/packages/core/client/src/application/__tests__/Application.test.tsx +++ b/packages/core/client/src/application/__tests__/Application.test.tsx @@ -467,7 +467,7 @@ describe('Application', () => { .toMatchInlineSnapshot(` [ { - "label": "TestComponent", + "label": "{{t("TestComponent")}}", "useProps": [Function], "value": "TestComponent", }, diff --git a/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx b/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx index 57a9e9d496..7ead4902f6 100644 --- a/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx +++ b/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx @@ -71,11 +71,13 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "parent-2-item1-0", "label": "item1", "onClick": [Function], + "style": undefined, }, { "key": "parent-2-item2-1", "label": "item2", "onClick": [Function], + "style": undefined, }, { "associationField": "a.b", @@ -139,6 +141,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "group-0-Item 1-0", "label": "Item 1", "onClick": [Function], + "style": undefined, }, ], "key": "group-0", @@ -151,6 +154,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "parent-item-group-1-Item 1-0", "label": "Item 1", "onClick": [Function], + "style": undefined, }, ], "key": "parent-item-group-1", @@ -204,6 +208,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-1-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-1", @@ -215,6 +220,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-2-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-2", @@ -226,6 +232,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-3-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-3", @@ -289,11 +296,13 @@ describe('useSchemaInitializerMenuItems', () => { "key": 1, "label": "item1", "onClick": [Function], + "style": undefined, }, { "key": 2, "label": "item2", "onClick": [Function], + "style": undefined, }, ] `); diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx index 9181354869..5a9baacc20 100644 --- a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx @@ -15,7 +15,7 @@ import { SchemaInitializerOptions } from '../types'; import { SchemaInitializerChildren } from './SchemaInitializerChildren'; import { SchemaInitializerDivider } from './SchemaInitializerDivider'; import { useSchemaInitializerStyles } from './style'; - +import { useMenuSearch } from './SchemaInitializerItemSearchFields'; export interface SchemaInitializerItemGroupProps { title: string; children?: SchemaInitializerOptions['items']; @@ -44,7 +44,14 @@ export const SchemaInitializerItemGroup: FC = ( /** * @internal */ + export const SchemaInitializerItemGroupInternal = () => { - const itemConfig = useSchemaInitializerItem(); - return ; + const itemConfig: any = useSchemaInitializerItem(); + + const searchedChildren = useMenuSearch(itemConfig); + if (itemConfig.name !== 'displayFields') { + return ; + } + /* eslint-disable react/no-children-prop */ + return ; }; diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx new file mode 100644 index 0000000000..b6016f57cd --- /dev/null +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx @@ -0,0 +1,183 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { uid } from '@formily/shared'; +import { Divider, Empty, Input, MenuProps } from 'antd'; +import React, { useEffect, useMemo, useState, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCompile } from '../../../'; + +function getPrefixAndCompare(a, b) { + const prefixA = a.replace(/-displayCollectionFields$/, ''); + const prefixB = b.replace(/-displayCollectionFields$/, ''); + + // 判断 a 是否包含 b,如果包含则返回 false,否则返回 true + return !prefixA.includes(prefixB); +} + +export const SearchFields = ({ value: outValue, onChange, name }) => { + const { t } = useTranslation(); + const [value, setValue] = useState(outValue); + const inputRef = useRef(''); + + // 生成唯一的ID用于区分不同层级的SearchFields + const uniqueId = useRef(`${name || Math.random().toString(10).substr(2, 9)}`); + + useEffect(() => { + setValue(outValue); + }, [outValue]); + + useEffect(() => { + const focusInput = () => { + if ( + document.activeElement?.id !== inputRef.current.input.id && + getPrefixAndCompare(document.activeElement?.id, inputRef.current.input.id) + ) { + inputRef.current?.focus(); + } + }; + + // 观察当前元素是否在视图中 + const observer = new IntersectionObserver((entries) => { + if (entries.some((v) => v.isIntersecting)) { + focusInput(); + } + }); + if (inputRef.current?.input) { + inputRef.current.input.id = uniqueId.current; // 设置唯一ID + observer.observe(inputRef.current.input); + } + + return () => { + observer.disconnect(); + }; + }, []); + + const compositionRef = useRef(false); + + const handleChange = (e: React.ChangeEvent) => { + if (!compositionRef.current) { + onChange(e.target.value); + setValue(e.target.value); + } + }; + const Composition = (e: React.CompositionEvent | any) => { + if (e.type === 'compositionend') { + compositionRef.current = false; + handleChange(e); + } else { + compositionRef.current = true; + } + }; + return ( +
e.stopPropagation()}> + { + e.stopPropagation(); + }} + onChange={handleChange} + onCompositionStart={Composition} + onCompositionEnd={Composition} + onCompositionUpdate={Composition} + /> + +
+ ); +}; + +export const useMenuSearch = (props: { children: any[]; showType?: boolean; hideSearch?: boolean; name?: string }) => { + const { children, showType, hideSearch, name } = props; + const items = children?.concat?.() || []; + const [searchValue, setSearchValue] = useState(null); + const compile = useCompile(); + + // 处理搜索逻辑 + const limitedSearchedItems = useMemo(() => { + if (!searchValue || searchValue === '') { + return items; + } + const lowerSearchValue = searchValue.toLocaleLowerCase(); + return items.filter((item) => { + return ( + (item.title || item.label) && + String(compile(item.title || item.label)) + .toLocaleLowerCase() + .includes(lowerSearchValue) + ); + }); + }, [searchValue, items]); + + // 最终结果项 + const resultItems = useMemo(() => { + const res = []; + if (!hideSearch && (items.length > 10 || searchValue)) { + res.push({ + key: `search-${uid()}`, + Component: () => ( + { + setSearchValue(val); + }} + /> + ), + onClick({ domEvent }) { + domEvent.stopPropagation(); + }, + ...(showType ? { isMenuType: true } : {}), + }); + } + + if (limitedSearchedItems.length > 0) { + res.push(...limitedSearchedItems); + } else { + res.push({ + key: 'empty', + style: { + height: 150, + }, + Component: () => ( +
e.stopPropagation()}> + +
+ ), + ...(showType ? { isMenuType: true } : {}), + }); + } + + return res; + }, [hideSearch, limitedSearchedItems, searchValue, showType]); + + const result = processedResult(resultItems, showType, hideSearch, name); + + return children ? result : undefined; +}; + +// 处理嵌套子菜单 +const processedResult = (resultItems, showType, hideSearch, name) => { + return resultItems.map((item: any) => { + if (['subMenu', 'itemGroup'].includes(item.type)) { + const childItems = useMenuSearch({ + children: item.children, + showType, + hideSearch, + name: item.name, + }); + return { ...item, children: childItems }; + } + return item; + }); +}; diff --git a/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx b/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx index 10573983f5..6d374ba82d 100644 --- a/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx +++ b/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx @@ -101,6 +101,7 @@ export function useGetSchemaInitializerMenuItems(onClick?: (args: any) => void) onClick: handleClick, } : { + style: item.style, key, label, onClick: handleClick, diff --git a/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx b/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx index 61d4eabd9a..26b52739bc 100644 --- a/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx +++ b/packages/core/client/src/application/schema-settings/components/SchemaSettingsChildren.tsx @@ -83,7 +83,7 @@ export const SchemaSettingsChildren: FC = (props) = // 此时如果使用 item.name 作为 key,会导致 React 认为其前后是同一个组件;因为 SchemaSettingsChild 的某些 hooks 是通过 props 传入的, // 两次渲染之间 props 可能发生变化,就可能报 hooks 调用顺序的错误。所以这里使用 fieldComponentName 和 item.name 拼成 // 一个不会重复的 key,保证每次渲染都是新的组件。 - const key = `${fieldComponentName ? fieldComponentName + '-' : ''}${item.name}`; + const key = `${fieldComponentName ? fieldComponentName + '-' : ''}${item?.name}`; return ( ahooks); requirejs.define('@emotion/css', () => emotionCss); requirejs.define('dayjs', () => dayjs); + requirejs.define('file-saver', () => FileSaver); } diff --git a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx index cb050435cb..9c1a6c2293 100644 --- a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx +++ b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx @@ -142,6 +142,8 @@ export const useDetailsBlockProps = () => { .reset() .then(() => { ctx.form.setInitialValues(data || {}); + ctx.form.setValues(data || {}); + // Using `ctx.form.setValues(data || {});` here may cause an internal infinite loop in Formily }) .catch(console.error); diff --git a/packages/core/client/src/block-provider/FormBlockProvider.tsx b/packages/core/client/src/block-provider/FormBlockProvider.tsx index d2abb74276..fcd16e041a 100644 --- a/packages/core/client/src/block-provider/FormBlockProvider.tsx +++ b/packages/core/client/src/block-provider/FormBlockProvider.tsx @@ -24,6 +24,8 @@ import { useActionContext } from '../schema-component'; import { BlockProvider, useBlockRequestContext } from './BlockProvider'; import { TemplateBlockProvider } from './TemplateBlockProvider'; import { FormActiveFieldsProvider } from './hooks/useFormActiveFields'; +import { useDesignable } from '../schema-component'; +import { useCollectionRecordData } from '../data-source'; export const FormBlockContext = createContext<{ form?: any; @@ -123,6 +125,18 @@ export const useIsDetailBlock = () => { export const FormBlockProvider = withDynamicSchemaProps((props) => { const parentRecordData = useCollectionParentRecordData(); const { parentRecord } = props; + const record = useCollectionRecordData(); + const { association } = props; + const cm = useCollectionManager(); + const { __collection } = record || {}; + const { designable } = useDesignable(); + const collection = props.collection || cm.getCollection(association).name; + + if (!designable && __collection) { + if (__collection !== collection) { + return null; + } + } return ( diff --git a/packages/core/client/src/block-provider/TableSelectorProvider.tsx b/packages/core/client/src/block-provider/TableSelectorProvider.tsx index 9c8e9b72e1..95a4252d4f 100644 --- a/packages/core/client/src/block-provider/TableSelectorProvider.tsx +++ b/packages/core/client/src/block-provider/TableSelectorProvider.tsx @@ -297,7 +297,7 @@ export const useTableSelectorProps = () => { field.value = data; field?.setInitialValue?.(data); field.data = field.data || {}; - field.data.selectedRowKeys = ctx?.field?.data?.selectedRowKeys; + field.data.selectedRowKeys = []; field.componentProps.pagination = field.componentProps.pagination || {}; field.componentProps.pagination.pageSize = ctx?.service?.data?.meta?.pageSize; field.componentProps.pagination.total = ctx?.service?.data?.meta?.count; diff --git a/packages/core/client/src/block-provider/__tests__/hooks/getAppends.test.ts b/packages/core/client/src/block-provider/__tests__/hooks/getAppends.test.ts new file mode 100644 index 0000000000..2ae35a9187 --- /dev/null +++ b/packages/core/client/src/block-provider/__tests__/hooks/getAppends.test.ts @@ -0,0 +1,248 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Schema } from '@formily/json-schema'; +import { describe, expect, it } from 'vitest'; +import { getAppends } from '../../hooks/index'; + +describe('getAppends', () => { + const mockGetCollectionJoinField = (name: string) => { + const fields = { + 'users.profile': { + type: 'hasOne', + target: 'profiles', + }, + 'users.posts': { + type: 'hasMany', + target: 'posts', + }, + 'posts.author': { + type: 'belongsTo', + target: 'users', + }, + 'users.roles': { + type: 'belongsToMany', + target: 'roles', + }, + 'users.categories': { + type: 'belongsToArray', + target: 'categories', + }, + }; + return fields[name]; + }; + + const mockGetCollection = (name: string) => { + const collections = { + categories: { + template: 'tree', + }, + users: { + template: 'general', + }, + }; + return collections[name]; + }; + + const createSchema = (properties) => { + return new Schema({ + properties, + }); + }; + + it('should handle basic association fields', () => { + const schema = createSchema({ + profile: { + 'x-component': 'Input', + 'x-collection-field': 'users.profile', + name: 'profile', + }, + }); + + const updateAssociationValues = new Set(); + const appends = new Set(); + + getAppends({ + schema, + prefix: '', + updateAssociationValues, + appends, + getCollectionJoinField: mockGetCollectionJoinField, + getCollection: mockGetCollection, + dataSource: 'main', + }); + + expect(Array.from(appends)).toEqual(['profile']); + expect(Array.from(updateAssociationValues)).toEqual([]); + }); + + it('should handle tree collection fields', () => { + const schema = createSchema({ + categories: { + 'x-component': 'Input', + 'x-collection-field': 'users.categories', + name: 'categories', + }, + }); + + const updateAssociationValues = new Set(); + const appends = new Set(); + + getAppends({ + schema, + prefix: '', + updateAssociationValues, + appends, + getCollectionJoinField: mockGetCollectionJoinField, + getCollection: mockGetCollection, + dataSource: 'main', + }); + + expect(Array.from(appends)).toEqual(['categories', 'categories.parent(recursively=true)']); + }); + + it('should handle nested fields with sorting', () => { + const schema = createSchema({ + posts: { + 'x-component': 'Input', + 'x-collection-field': 'users.posts', + 'x-component-props': { + sortArr: 'createdAt', + }, + name: 'posts', + }, + }); + + const updateAssociationValues = new Set(); + const appends = new Set(); + + getAppends({ + schema, + prefix: '', + updateAssociationValues, + appends, + getCollectionJoinField: mockGetCollectionJoinField, + getCollection: mockGetCollection, + dataSource: 'main', + }); + + expect(Array.from(appends)).toEqual(['posts(sort=createdAt)']); + }); + + it('should handle nested SubTable mode', () => { + const schema = createSchema({ + posts: { + 'x-component': 'Input', + 'x-collection-field': 'users.posts', + 'x-component-props': { + mode: 'SubTable', + }, + name: 'posts', + properties: { + author: { + 'x-component': 'Input', + 'x-collection-field': 'posts.author', + name: 'author', + }, + }, + }, + }); + + const updateAssociationValues = new Set(); + const appends = new Set(); + + getAppends({ + schema, + prefix: '', + updateAssociationValues, + appends, + getCollectionJoinField: mockGetCollectionJoinField, + getCollection: mockGetCollection, + dataSource: 'main', + }); + + expect(Array.from(appends)).toEqual(['posts', 'posts.author']); + expect(Array.from(updateAssociationValues)).toEqual(['posts']); + }); + + it('should ignore TableField components', () => { + const schema = createSchema({ + posts: { + 'x-component': 'TableField', + 'x-collection-field': 'users.posts', + name: 'posts', + }, + }); + + const updateAssociationValues = new Set(); + const appends = new Set(); + + getAppends({ + schema, + prefix: '', + updateAssociationValues, + appends, + getCollectionJoinField: mockGetCollectionJoinField, + getCollection: mockGetCollection, + dataSource: 'main', + }); + + expect(Array.from(appends)).toEqual([]); + expect(Array.from(updateAssociationValues)).toEqual([]); + }); + + it('should ignore Kanban.CardViewer components', () => { + const schema = createSchema({ + cardViewer: { + 'x-component': 'Kanban.CardViewer', + name: 'cardViewer', + properties: { + drawer: { + name: 'drawer', + type: 'void', + properties: { + grid: { + name: 'grid', + type: 'void', + properties: { + field1: { + 'x-component': 'Input', + 'x-collection-field': 'users.posts', + name: 'field1', + }, + field2: { + 'x-component': 'Input', + 'x-collection-field': 'posts.author', + name: 'field2', + }, + }, + }, + }, + }, + }, + }, + }); + + const updateAssociationValues = new Set(); + const appends = new Set(); + + getAppends({ + schema, + prefix: '', + updateAssociationValues, + appends, + getCollectionJoinField: mockGetCollectionJoinField, + getCollection: mockGetCollection, + dataSource: 'main', + }); + + expect(Array.from(appends)).toEqual([]); + expect(Array.from(updateAssociationValues)).toEqual([]); + }); +}); diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index cc15e0fe2e..f06e3f72b8 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -35,7 +35,7 @@ import { import { useAPIClient, useRequest } from '../../api-client'; import { useNavigateNoUpdate } from '../../application/CustomRouterContextProvider'; import { useFormBlockContext } from '../../block-provider/FormBlockProvider'; -import { useCollectionManager_deprecated, useCollection_deprecated } from '../../collection-manager'; +import { CollectionOptions, useCollectionManager_deprecated, useCollection_deprecated } from '../../collection-manager'; import { DataBlock, useFilterBlock } from '../../filter-provider/FilterProvider'; import { mergeFilter, transformToFilter } from '../../filter-provider/utils'; import { useTreeParentRecord } from '../../modules/blocks/data-blocks/table/TreeRecordProvider'; @@ -1284,7 +1284,7 @@ export const useOptionalFieldList = () => { const isOptionalField = (field) => { const optionalInterfaces = ['select', 'multipleSelect', 'checkbox', 'checkboxGroup', 'chinaRegion']; - return optionalInterfaces.includes(field.interface) && field.uiSchema.enum; + return optionalInterfaces.includes(field?.interface) && field?.uiSchema?.enum; }; export const useAssociationFilterBlockProps = () => { @@ -1329,7 +1329,7 @@ export const useAssociationFilterBlockProps = () => { useEffect(() => { // 由于选项字段不需要触发当前请求,所以请求单独在关系字段的时候触发 - if (!isOptionalField(collectionField) && parseVariableLoading === false) { + if (collectionField && !isOptionalField(collectionField) && parseVariableLoading === false) { run(); } @@ -1494,90 +1494,137 @@ export function getAssociationPath(str) { return str; } -export const useAssociationNames = (dataSource?: string) => { - let updateAssociationValues = new Set([]); - let appends = new Set([]); - const { getCollectionJoinField, getCollection } = useCollectionManager_deprecated(dataSource); - const fieldSchema = useFieldSchema(); - const _getAssociationAppends = (schema, str) => { - schema.reduceProperties((pre, s) => { - const prefix = pre || str; - const collectionField = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field'], dataSource); - const isAssociationSubfield = s.name.includes('.'); - const isAssociationField = - collectionField && - ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(collectionField.type); +export const getAppends = ({ + schema, + prefix: defaultPrefix, + updateAssociationValues, + appends, + getCollectionJoinField, + getCollection, + dataSource, +}: { + schema: any; + prefix: string; + updateAssociationValues: Set; + appends: Set; + getCollectionJoinField: (name: string, dataSource: string) => any; + getCollection: (name: any, customDataSource?: string) => CollectionOptions; + dataSource: string; +}) => { + schema.reduceProperties((pre, s) => { + const prefix = pre || defaultPrefix; + const collectionField = s['x-collection-field'] && getCollectionJoinField(s['x-collection-field'], dataSource); + const isAssociationSubfield = s.name.includes('.'); + const isAssociationField = + collectionField && + ['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(collectionField.type); - // 根据联动规则中条件的字段获取一些 appends - // 需要排除掉子表格和子表单中的联动规则 - if (s['x-linkage-rules'] && !isSubMode(s)) { - const collectAppends = (obj) => { - const type = Object.keys(obj)[0] || '$and'; - const list = obj[type]; + // 根据联动规则中条件的字段获取一些 appends + // 需要排除掉子表格和子表单中的联动规则 + if (s['x-linkage-rules'] && !isSubMode(s)) { + const collectAppends = (obj) => { + const type = Object.keys(obj)[0] || '$and'; + const list = obj[type]; - list.forEach((item) => { - if ('$and' in item || '$or' in item) { - return collectAppends(item); - } + list.forEach((item) => { + if ('$and' in item || '$or' in item) { + return collectAppends(item); + } - const fieldNames = getTargetField(item); + const fieldNames = getTargetField(item); - // 只应该收集关系字段,只有大于 1 的时候才是关系字段 - if (fieldNames.length > 1) { - appends.add(fieldNames.join('.')); - } - }); - }; + // 只应该收集关系字段,只有大于 1 的时候才是关系字段 + if (fieldNames.length > 1) { + appends.add(fieldNames.join('.')); + } + }); + }; - const rules = s['x-linkage-rules']; - rules.forEach(({ condition }) => { - collectAppends(condition); + const rules = s['x-linkage-rules']; + rules.forEach(({ condition }) => { + collectAppends(condition); + }); + } + + const isTreeCollection = + isAssociationField && getCollection(collectionField.target, dataSource)?.template === 'tree'; + + if (collectionField && (isAssociationField || isAssociationSubfield) && s['x-component'] !== 'TableField') { + const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name; + const path = prefix === '' || !prefix ? fieldPath : prefix + '.' + fieldPath; + if (isTreeCollection) { + appends.add(path); + appends.add(`${path}.parent` + '(recursively=true)'); + } else { + if (s['x-component-props']?.sortArr) { + const sort = s['x-component-props']?.sortArr; + appends.add(`${path}(sort=${sort})`); + } else { + appends.add(path); + } + } + if (isSubMode(s)) { + updateAssociationValues.add(path); + const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name; + getAppends({ + schema: s, + prefix: bufPrefix, + updateAssociationValues, + appends, + getCollectionJoinField, + getCollection, + dataSource, }); } - const isTreeCollection = - isAssociationField && getCollection(collectionField.target, dataSource)?.template === 'tree'; - if (collectionField && (isAssociationField || isAssociationSubfield) && s['x-component'] !== 'TableField') { - const fieldPath = !isAssociationField && isAssociationSubfield ? getAssociationPath(s.name) : s.name; - const path = prefix === '' || !prefix ? fieldPath : prefix + '.' + fieldPath; - if (isTreeCollection) { - appends.add(path); - appends.add(`${path}.parent` + '(recursively=true)'); - } else { - if (s['x-component-props']?.sortArr) { - const sort = s['x-component-props']?.sortArr; - appends.add(`${path}(sort=${sort})`); - } else { - appends.add(path); - } - } - if (['Nester', 'SubTable', 'PopoverNester'].includes(s['x-component-props']?.mode)) { - updateAssociationValues.add(path); - const bufPrefix = prefix && prefix !== '' ? prefix + '.' + s.name : s.name; - _getAssociationAppends(s, bufPrefix); - } - } else if ( - ![ - 'ActionBar', - 'Action', - 'Action.Link', - 'Action.Modal', - 'Selector', - 'Viewer', - 'AddNewer', - 'AssociationField.Selector', - 'AssociationField.AddNewer', - 'TableField', - ].includes(s['x-component']) - ) { - _getAssociationAppends(s, str); - } - }, str); - }; + } else if ( + ![ + 'ActionBar', + 'Action', + 'Action.Link', + 'Action.Modal', + 'Selector', + 'Viewer', + 'AddNewer', + 'AssociationField.Selector', + 'AssociationField.AddNewer', + 'TableField', + 'Kanban.CardViewer', + ].includes(s['x-component']) + ) { + getAppends({ + schema: s, + prefix: defaultPrefix, + updateAssociationValues, + appends, + getCollectionJoinField, + getCollection, + dataSource, + }); + } + }, defaultPrefix); +}; + +export const useAssociationNames = (dataSource?: string) => { + const { getCollectionJoinField, getCollection } = useCollectionManager_deprecated(dataSource); + const fieldSchema = useFieldSchema(); + const getAssociationAppends = () => { - updateAssociationValues = new Set([]); - appends = new Set([]); - _getAssociationAppends(fieldSchema, ''); + const updateAssociationValues = new Set([]); + let appends = new Set([]); + + getAppends({ + schema: fieldSchema, + prefix: '', + updateAssociationValues, + appends, + getCollectionJoinField, + getCollection, + dataSource, + }); appends = fillParentFields(appends); + + console.log('appends', appends); + return { appends: [...appends], updateAssociationValues: [...updateAssociationValues] }; }; diff --git a/packages/core/client/src/block-provider/hooks/useBlockHeightProps.tsx b/packages/core/client/src/block-provider/hooks/useBlockHeightProps.tsx index fedee68b8e..ddbb92a610 100644 --- a/packages/core/client/src/block-provider/hooks/useBlockHeightProps.tsx +++ b/packages/core/client/src/block-provider/hooks/useBlockHeightProps.tsx @@ -35,7 +35,7 @@ export const getPageSchema = (schema) => { return getPageSchema(schema.parent); }; -const getCardItemSchema = (schema) => { +export const getCardItemSchema = (schema) => { if (!schema) return null; if (['BlockItem', 'CardItem'].includes(schema['x-component'])) { return schema; diff --git a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx index 81a0fcdfcb..b6b5c796e4 100644 --- a/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx +++ b/packages/core/client/src/collection-manager/CollectionManagerProvider.tsx @@ -28,16 +28,7 @@ export const CollectionManagerProvider_deprecated: React.FC { - const api = useAPIClient(); const dm = useDataSourceManager(); const { refreshCH } = useCollectionHistory(); @@ -46,26 +37,13 @@ export const RemoteCollectionManagerProvider = (props: any) => { }>(() => { return dm.reload().then(refreshCH); }); - const result = useRequest<{ - data: any; - }>(coptions); const { render } = useAppSpin(); - const refreshCategory = useCallback(async () => { - const { data } = await api.request(coptions); - result.mutate(data); - return data?.data || []; - }, [result]); - if (service.loading) { return render(); } - return ( - - - - ); + return ; }; export const CollectionCategoriesProvider = (props) => { diff --git a/packages/core/client/src/collection-manager/collectionPlugin.ts b/packages/core/client/src/collection-manager/collectionPlugin.ts index eedf07ced5..9c80675d4d 100644 --- a/packages/core/client/src/collection-manager/collectionPlugin.ts +++ b/packages/core/client/src/collection-manager/collectionPlugin.ts @@ -18,7 +18,9 @@ import { ColorFieldInterface, CreatedAtFieldInterface, CreatedByFieldInterface, + DateFieldInterface, DatetimeFieldInterface, + DatetimeNoTzFieldInterface, EmailFieldInterface, IconFieldInterface, IdFieldInterface, @@ -30,29 +32,28 @@ import { M2OFieldInterface, MarkdownFieldInterface, MultipleSelectFieldInterface, + NanoidFieldInterface, NumberFieldInterface, O2MFieldInterface, O2OFieldInterface, - OHOFieldInterface, OBOFieldInterface, + OHOFieldInterface, PasswordFieldInterface, PercentFieldInterface, PhoneFieldInterface, RadioGroupFieldInterface, RichTextFieldInterface, SelectFieldInterface, + SortFieldInterface, SubTableFieldInterface, TableoidFieldInterface, TextareaFieldInterface, TimeFieldInterface, + UnixTimestampFieldInterface, UpdatedAtFieldInterface, UpdatedByFieldInterface, UrlFieldInterface, UUIDFieldInterface, - NanoidFieldInterface, - UnixTimestampFieldInterface, - DateFieldInterface, - DatetimeNoTzFieldInterface, } from './interfaces'; import { GeneralCollectionTemplate, @@ -67,17 +68,11 @@ class MainDataSource extends DataSource { async getDataSource() { const service = await this.app.apiClient.request({ resource: 'collections', - action: 'list', - params: { - paginate: false, - appends: ['fields', 'category'], - filter: { - // inherit: false, - }, - sort: ['sort'], - }, + action: 'listMeta', }); + const collections = service?.data?.data || []; + return { collections, }; diff --git a/packages/core/client/src/collection-manager/interfaces/id.ts b/packages/core/client/src/collection-manager/interfaces/id.ts index 2dfba0a928..f4180bec94 100644 --- a/packages/core/client/src/collection-manager/interfaces/id.ts +++ b/packages/core/client/src/collection-manager/interfaces/id.ts @@ -31,7 +31,7 @@ export class IdFieldInterface extends CollectionFieldInterface { 'x-read-pretty': true, }, }; - availableTypes = ['bigInt', 'integer', 'string']; + availableTypes = ['bigInt', 'integer']; properties = { 'uiSchema.title': { type: 'string', diff --git a/packages/core/client/src/collection-manager/interfaces/index.ts b/packages/core/client/src/collection-manager/interfaces/index.ts index c453aa4b0e..67383d52c2 100644 --- a/packages/core/client/src/collection-manager/interfaces/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/index.ts @@ -47,3 +47,5 @@ export * from './nanoid'; export * from './unixTimestamp'; export * from './dateOnly'; export * from './datetimeNoTz'; + +export { getUniqueKeyFromCollection } from './utils'; diff --git a/packages/core/client/src/collection-manager/interfaces/nanoid.ts b/packages/core/client/src/collection-manager/interfaces/nanoid.ts index 109ae8296c..79599ab6c5 100644 --- a/packages/core/client/src/collection-manager/interfaces/nanoid.ts +++ b/packages/core/client/src/collection-manager/interfaces/nanoid.ts @@ -24,7 +24,7 @@ export class NanoidFieldInterface extends CollectionFieldInterface { 'x-component': 'NanoIDInput', }, }; - availableTypes = ['string', 'nanoid']; + availableTypes = ['nanoid']; properties = { 'uiSchema.title': { type: 'string', diff --git a/packages/core/client/src/collection-manager/interfaces/number.ts b/packages/core/client/src/collection-manager/interfaces/number.ts index be89ced87c..13b534dbf4 100644 --- a/packages/core/client/src/collection-manager/interfaces/number.ts +++ b/packages/core/client/src/collection-manager/interfaces/number.ts @@ -47,6 +47,9 @@ export class NumberFieldInterface extends CollectionFieldInterface { { value: '0.001', label: '1.000' }, { value: '0.0001', label: '1.0000' }, { value: '0.00001', label: '1.00000' }, + { value: '0.000001', label: '1.000000' }, + { value: '0.0000001', label: '1.0000000' }, + { value: '0.00000001', label: '1.00000000' }, ], }, }; diff --git a/packages/core/client/src/collection-manager/interfaces/o2m.tsx b/packages/core/client/src/collection-manager/interfaces/o2m.tsx index bfd664a03d..53b32bda82 100644 --- a/packages/core/client/src/collection-manager/interfaces/o2m.tsx +++ b/packages/core/client/src/collection-manager/interfaces/o2m.tsx @@ -117,7 +117,7 @@ export class O2MFieldInterface extends CollectionFieldInterface { type: 'string', title: '{{t("Target collection")}}', required: true, - 'x-reactions': ['{{useAsyncDataSource(loadCollections, ["file"])}}'], + 'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'], 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-disabled': '{{ !createOnly }}', diff --git a/packages/core/client/src/collection-manager/interfaces/o2o.tsx b/packages/core/client/src/collection-manager/interfaces/o2o.tsx index 16f497ed17..c8eb6d3428 100644 --- a/packages/core/client/src/collection-manager/interfaces/o2o.tsx +++ b/packages/core/client/src/collection-manager/interfaces/o2o.tsx @@ -118,7 +118,7 @@ export class O2OFieldInterface extends CollectionFieldInterface { type: 'string', title: '{{t("Target collection")}}', required: true, - 'x-reactions': ['{{useAsyncDataSource(loadCollections, ["file"])}}'], + 'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'], 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-disabled': '{{ !createOnly }}', @@ -306,7 +306,7 @@ export class OHOFieldInterface extends CollectionFieldInterface { type: 'string', title: '{{t("Target collection")}}', required: true, - 'x-reactions': ['{{useAsyncDataSource(loadCollections, ["file"])}}'], + 'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'], 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-disabled': '{{ !createOnly }}', diff --git a/packages/core/client/src/collection-manager/interfaces/properties/index.ts b/packages/core/client/src/collection-manager/interfaces/properties/index.ts index 82d08c2ad9..b3cb9ff58b 100644 --- a/packages/core/client/src/collection-manager/interfaces/properties/index.ts +++ b/packages/core/client/src/collection-manager/interfaces/properties/index.ts @@ -147,7 +147,6 @@ export const reverseFieldProperties: Record = { reverse: { type: 'void', 'x-component': 'div', - 'x-hidden': '{{ !showReverseFieldConfig }}', properties: { autoCreateReverseField: { type: 'boolean', @@ -198,6 +197,12 @@ export const reverseFieldProperties: Record = { }, }, }, + (field) => { + const values = field.form.values; + const { reverseField } = values; + field.value = !!reverseField?.key; + field.disabled = !!reverseField?.key; + }, ], }, 'reverseField.type': { @@ -211,6 +216,7 @@ export const reverseFieldProperties: Record = { required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', + 'x-disabled': '{{ !showReverseFieldConfig }}', }, 'reverseField.name': { type: 'string', @@ -219,6 +225,7 @@ export const reverseFieldProperties: Record = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': 'uid', + 'x-disabled': '{{ !showReverseFieldConfig }}', description: "{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with an letter.')}}", }, diff --git a/packages/core/client/src/collection-manager/interfaces/uuid.ts b/packages/core/client/src/collection-manager/interfaces/uuid.ts index ee233de5c3..374e0746a8 100644 --- a/packages/core/client/src/collection-manager/interfaces/uuid.ts +++ b/packages/core/client/src/collection-manager/interfaces/uuid.ts @@ -26,7 +26,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface { 'x-validator': 'uuid', }, }; - availableTypes = ['string', 'uuid']; + availableTypes = ['uid', 'uuid']; properties = { 'uiSchema.title': { type: 'string', diff --git a/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts b/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts index 69de8c8f97..a849280f9f 100644 --- a/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts +++ b/packages/core/client/src/data-source/__tests__/collection-field-interface/CollectionFieldInterfaceManager.test.ts @@ -156,7 +156,7 @@ describe('CollectionFieldInterfaceManager', () => { expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` [ { - "label": "A", + "label": "{{t("A")}}", "useProps": [Function], "value": "A", }, @@ -190,7 +190,7 @@ describe('CollectionFieldInterfaceManager', () => { expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` [ { - "label": "A", + "label": "{{t("A")}}", "useProps": [Function], "value": "A", }, @@ -268,7 +268,7 @@ describe('CollectionFieldInterfaceManager', () => { expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` [ { - "label": "B", + "label": "{{t("B")}}", "useProps": [Function], "value": "A.B", }, @@ -292,7 +292,7 @@ describe('CollectionFieldInterfaceManager', () => { }; componentOptions = [ { - label: 'A', + label: '{{t("A")}}', value: 'A', }, ]; @@ -308,7 +308,7 @@ describe('CollectionFieldInterfaceManager', () => { expect(fieldInterface.componentOptions).toMatchInlineSnapshot(` [ { - "label": "A", + "label": "{{t("A")}}", "value": "A", }, { diff --git a/packages/core/client/src/data-source/__tests__/collection/Collection.test.tsx b/packages/core/client/src/data-source/__tests__/collection/Collection.test.tsx index b929f8ffc5..020e68afc4 100644 --- a/packages/core/client/src/data-source/__tests__/collection/Collection.test.tsx +++ b/packages/core/client/src/data-source/__tests__/collection/Collection.test.tsx @@ -181,6 +181,23 @@ describe('Collection', () => { }); }); + describe('getFilterTargetKey()', () => { + test('not set as id', () => { + const collection = getCollection({ name: 'test' }); + expect(collection.getFilterTargetKey()).toBe('id'); + }); + + test('single ftk', () => { + const collection = getCollection({ name: 'test', filterTargetKey: 'a' }); + expect(collection.getFilterTargetKey()).toBe('a'); + }); + + test('multiple ftk', () => { + const collection = getCollection({ name: 'test', filterTargetKey: ['a', 'b'] }); + expect(collection.getFilterTargetKey()).toMatchObject(['a', 'b']); + }); + }); + test('properties', () => { const app = new Application({ dataSourceManager: { diff --git a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts index befd3def02..49eeddeb1d 100644 --- a/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts +++ b/packages/core/client/src/data-source/collection-field-interface/CollectionFieldInterface.ts @@ -8,10 +8,11 @@ */ import type { ISchema } from '@formily/react'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, capitalize } from 'lodash'; import type { CollectionFieldOptions } from '../collection'; import { CollectionFieldInterfaceManager } from './CollectionFieldInterfaceManager'; import { defaultProps } from '../../collection-manager/interfaces/properties'; +import { tval } from '@nocobase/utils/client'; export type CollectionFieldInterfaceFactory = new ( collectionFieldInterfaceManager: CollectionFieldInterfaceManager, ) => CollectionFieldInterface; @@ -71,9 +72,11 @@ export abstract class CollectionFieldInterface { const xComponent = this.default?.uiSchema?.['x-component']; const componentProps = this.default?.uiSchema?.['x-component-props']; if (xComponent) { + const schemaType = this.default?.uiSchema?.type || 'string'; + const label = tval(xComponent.startsWith('Input') ? capitalize(schemaType) : xComponent.split('.').pop()); this.componentOptions = [ { - label: xComponent.split('.').pop(), + label, value: xComponent, useProps() { return componentProps || {}; diff --git a/packages/core/client/src/data-source/collection-field/CollectionField.tsx b/packages/core/client/src/data-source/collection-field/CollectionField.tsx index 3f300fd093..648f15051d 100644 --- a/packages/core/client/src/data-source/collection-field/CollectionField.tsx +++ b/packages/core/client/src/data-source/collection-field/CollectionField.tsx @@ -8,10 +8,11 @@ */ import { Field } from '@formily/core'; -import { connect, useField, useFieldSchema } from '@formily/react'; +import { connect, Schema, useField, useFieldSchema } from '@formily/react'; +import { untracked } from '@formily/reactive'; import { merge } from '@formily/shared'; import { concat } from 'lodash'; -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useEffect } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useFormBlockContext } from '../../block-provider/FormBlockProvider'; import { useDynamicComponentProps } from '../../hoc/withDynamicSchemaProps'; @@ -24,54 +25,48 @@ type Props = { children?: React.ReactNode; }; +const setFieldProps = (field: Field, key: string, value: any) => { + untracked(() => { + if (field[key] === undefined) { + field[key] = value; + } + }); +}; + +const setRequired = (field: Field, fieldSchema: Schema, uiSchema: Schema) => { + if (typeof fieldSchema['required'] === 'undefined') { + field.required = !!uiSchema['required']; + } +}; + /** * TODO: 初步适配 * @internal */ export const CollectionFieldInternalField: React.FC = (props: Props) => { - const { component } = props; const compile = useCompile(); const field = useField(); const fieldSchema = useFieldSchema(); - const collectionField = useCollectionField(); - const { uiSchema: uiSchemaOrigin, defaultValue } = collectionField; + const { uiSchema: uiSchemaOrigin, defaultValue } = useCollectionField(); const { isAllowToSetDefaultValue } = useIsAllowToSetDefaultValue(); - const uiSchema = useMemo(() => compile(uiSchemaOrigin), [JSON.stringify(uiSchemaOrigin)]); const Component = useComponent( - fieldSchema['x-component-props']?.['component'] || uiSchema?.['x-component'] || 'Input', + fieldSchema['x-component-props']?.['component'] || uiSchemaOrigin?.['x-component'] || 'Input', ); - const setFieldProps = useCallback( - (key, value) => { - field[key] = typeof field[key] === 'undefined' ? value : field[key]; - }, - [field], - ); - const setRequired = useCallback(() => { - if (typeof fieldSchema['required'] === 'undefined') { - field.required = !!uiSchema['required']; - } - }, [fieldSchema, uiSchema]); const ctx = useFormBlockContext(); + const dynamicProps = useDynamicComponentProps(uiSchemaOrigin?.['x-use-component-props'], props); - const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props); - - useEffect(() => { - if (ctx?.field) { - ctx.field.added = ctx.field.added || new Set(); - ctx.field.added.add(fieldSchema.name); - } - }); // TODO: 初步适配 useEffect(() => { - if (!uiSchema) { + if (!uiSchemaOrigin) { return; } - setFieldProps('content', uiSchema['x-content']); - setFieldProps('title', uiSchema.title); - setFieldProps('description', uiSchema.description); + const uiSchema = compile(uiSchemaOrigin); + setFieldProps(field, 'content', uiSchema['x-content']); + setFieldProps(field, 'title', uiSchema.title); + setFieldProps(field, 'description', uiSchema.description); if (ctx?.form) { const defaultVal = isAllowToSetDefaultValue() ? fieldSchema.default || defaultValue : undefined; - defaultVal !== null && defaultVal !== undefined && setFieldProps('initialValue', defaultVal); + defaultVal !== null && defaultVal !== undefined && setFieldProps(field, 'initialValue', defaultVal); } if (!field.validator && (uiSchema['x-validator'] || fieldSchema['x-validator'])) { @@ -84,20 +79,21 @@ export const CollectionFieldInternalField: React.FC = (props: Props) => { if (fieldSchema['x-read-pretty'] === true) { field.readPretty = true; } - setRequired(); + setRequired(field, fieldSchema, uiSchema); // @ts-ignore field.dataSource = uiSchema.enum; const originalProps = compile(uiSchema['x-component-props']) || {}; field.componentProps = merge(originalProps, field.componentProps || {}, dynamicProps || {}); - }, [uiSchema]); + }, [uiSchemaOrigin]); - if (!uiSchema) return null; + if (!uiSchemaOrigin) return null; return ; }; export const CollectionField = connect((props) => { const fieldSchema = useFieldSchema(); + const field = useField(); return ( console.log(err)}> diff --git a/packages/core/client/src/data-source/collection/Collection.ts b/packages/core/client/src/data-source/collection/Collection.ts index 977a6769c2..b8cdc04525 100644 --- a/packages/core/client/src/data-source/collection/Collection.ts +++ b/packages/core/client/src/data-source/collection/Collection.ts @@ -164,6 +164,9 @@ export class Collection { return this.primaryKey; } + getFilterTargetKey() { + return this.filterTargetKey || this.getPrimaryKey() || 'id'; + } get inherits() { return this.options.inherits || []; @@ -250,6 +253,10 @@ export class Collection { return predicate ? filter(this.fields, predicate) : this.fields; } + getAllFields(predicate?: GetCollectionFieldPredicate) { + return this.getFields(predicate); + } + protected getFieldsMap() { if (!this.fieldsMap) { this.fieldsMap = this.getFields().reduce((memo, field) => { diff --git a/packages/core/client/src/data-source/collection/CollectionManager.ts b/packages/core/client/src/data-source/collection/CollectionManager.ts index 6f8bda7966..dc0ba90ff4 100644 --- a/packages/core/client/src/data-source/collection/CollectionManager.ts +++ b/packages/core/client/src/data-source/collection/CollectionManager.ts @@ -141,6 +141,10 @@ export class CollectionManager { return this.getCollection(collectionName)?.getFields(predicate) || []; } + getCollectionAllFields(collectionName: string, predicate?: GetCollectionFieldPredicate) { + return this.getCollection(collectionName)?.getAllFields(predicate) || []; + } + /** * @example * getFilterByTK('users', { id: 1 }); // 1 @@ -160,7 +164,6 @@ export class CollectionManager { ); return; } - const getTargetKey = (collection: Collection) => collection.filterTargetKey || collection.getPrimaryKey() || 'id'; const buildFilterByTk = (targetKey: string | string[], record: Record) => { if (Array.isArray(targetKey)) { @@ -175,7 +178,7 @@ export class CollectionManager { }; if (collectionOrAssociation instanceof Collection) { - const targetKey = getTargetKey(collectionOrAssociation); + const targetKey = collectionOrAssociation.getFilterTargetKey(); return buildFilterByTk(targetKey, collectionRecordOrAssociationRecord); } @@ -200,7 +203,7 @@ export class CollectionManager { ); return; } - const targetKey = getTargetKey(targetCollection); + const targetKey = targetCollection.getFilterTargetKey(); return buildFilterByTk(targetKey, collectionRecordOrAssociationRecord); } diff --git a/packages/core/client/src/data-source/data-block/DataBlockResourceProvider.tsx b/packages/core/client/src/data-source/data-block/DataBlockResourceProvider.tsx index 9bb4d8df08..2f1588c478 100644 --- a/packages/core/client/src/data-source/data-block/DataBlockResourceProvider.tsx +++ b/packages/core/client/src/data-source/data-block/DataBlockResourceProvider.tsx @@ -8,8 +8,8 @@ */ import { IResource } from '@nocobase/sdk'; -import React, { FC, ReactNode, createContext, useContext, useMemo } from 'react'; import { isArray } from 'lodash'; +import React, { FC, ReactNode, createContext, useContext, useMemo } from 'react'; import { useAPIClient } from '../../api-client'; import { useCollectionManager } from '../collection'; import { CollectionRecord } from '../collection-record'; diff --git a/packages/core/client/src/hoc/withDynamicSchemaProps.tsx b/packages/core/client/src/hoc/withDynamicSchemaProps.tsx index 2bd0782dbc..3d7bfdb8dc 100644 --- a/packages/core/client/src/hoc/withDynamicSchemaProps.tsx +++ b/packages/core/client/src/hoc/withDynamicSchemaProps.tsx @@ -27,29 +27,28 @@ const getHook = (str: string, scope: Record, allText: string) => { return res || useDefaultDynamicComponentProps; }; +const getUseDynamicProps = (useComponentPropsStr: string, scope: Record) => { + if (!useComponentPropsStr) { + return useDefaultDynamicComponentProps; + } + + if (_.isFunction(useComponentPropsStr)) { + return useComponentPropsStr; + } + + const pathList = useComponentPropsStr.split('.'); + let result; + + for (const item of pathList) { + result = getHook(item, result || scope, useComponentPropsStr); + } + + return result; +}; + export function useDynamicComponentProps(useComponentPropsStr?: string, props?: any) { const scope = useExpressionScope(); - - const useDynamicProps = useMemo(() => { - if (!useComponentPropsStr) { - return useDefaultDynamicComponentProps; - } - - if (_.isFunction(useComponentPropsStr)) { - return useComponentPropsStr; - } - - const pathList = useComponentPropsStr.split('.'); - let result; - - for (const item of pathList) { - result = getHook(item, result || scope, useComponentPropsStr); - } - - return result; - }, [useComponentPropsStr]); - - const res = useDynamicProps(props); + const res = getUseDynamicProps(useComponentPropsStr, scope)(props); return res; } diff --git a/packages/core/client/src/locale/cron/zh_CN.json b/packages/core/client/src/locale/cron/zh-CN.json similarity index 100% rename from packages/core/client/src/locale/cron/zh_CN.json rename to packages/core/client/src/locale/cron/zh-CN.json diff --git a/packages/core/client/src/locale/cron/zh_TW.json b/packages/core/client/src/locale/cron/zh-TW.json similarity index 100% rename from packages/core/client/src/locale/cron/zh_TW.json rename to packages/core/client/src/locale/cron/zh-TW.json diff --git a/packages/core/client/src/locale/en_US.json b/packages/core/client/src/locale/en-US.json similarity index 98% rename from packages/core/client/src/locale/en_US.json rename to packages/core/client/src/locale/en-US.json index 57e7f4ecf0..83f0759ff9 100644 --- a/packages/core/client/src/locale/en_US.json +++ b/packages/core/client/src/locale/en-US.json @@ -842,8 +842,12 @@ "is any of": "is any of", "Plugin dependency version mismatch": "Plugin dependency version mismatch", "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?", + "Allow multiple selection": "Allow multiple selection", + "Parent object": "Parent object", "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data", "Enable secondary confirmation": "Enable secondary confirmation", "Notification": "Notification", - "Ellipsis overflow content": "Ellipsis overflow content" + "Ellipsis overflow content": "Ellipsis overflow content", + "Hide column": "Hide column", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect." } diff --git a/packages/core/client/src/locale/es_ES.json b/packages/core/client/src/locale/es-ES.json similarity index 98% rename from packages/core/client/src/locale/es_ES.json rename to packages/core/client/src/locale/es-ES.json index 7e26cf7a99..d8488c2d29 100644 --- a/packages/core/client/src/locale/es_ES.json +++ b/packages/core/client/src/locale/es-ES.json @@ -766,5 +766,9 @@ "Clear default value": "Borrar valor por defecto", "Open in new window": "Abrir en una nueva ventana", "Sorry, the page you visited does not exist.": "Lo siento, la página que visitaste no existe.", - "Ellipsis overflow content": "Contenido de desbordamiento de elipsis" + "Allow multiple selection": "Permitir selección múltiple", + "Parent object": "Objeto padre", + "Ellipsis overflow content": "Contenido de desbordamiento de elipsis", + "Hide column": "Ocultar columna", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "En modo de configuración, toda la columna se vuelve transparente. En modo de no configuración, toda la columna se ocultará. Incluso si toda la columna está oculta, sus valores predeterminados configurados y otras configuraciones seguirán tomando efecto." } diff --git a/packages/core/client/src/locale/fr_FR.json b/packages/core/client/src/locale/fr-FR.json similarity index 98% rename from packages/core/client/src/locale/fr_FR.json rename to packages/core/client/src/locale/fr-FR.json index 6f3af0f563..60320c9598 100644 --- a/packages/core/client/src/locale/fr_FR.json +++ b/packages/core/client/src/locale/fr-FR.json @@ -786,5 +786,9 @@ "Clear default value": "Effacer la valeur par défaut", "Open in new window": "Ouvrir dans une nouvelle fenêtre", "Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.", - "Ellipsis overflow content": "Contenu de débordement avec ellipse" + "Allow multiple selection": "Permettre la sélection multiple", + "Parent object": "Objet parent", + "Ellipsis overflow content": "Contenu de débordement avec ellipse", + "Hide column": "Masquer la colonne", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "En mode de configuration, toute la colonne devient transparente. En mode de non-configuration, toute la colonne sera masquée. Même si toute la colonne est masquée, ses valeurs par défaut configurées et les autres paramètres resteront toujours en vigueur." } diff --git a/packages/core/client/src/locale/ja_JP.json b/packages/core/client/src/locale/ja-JP.json similarity index 98% rename from packages/core/client/src/locale/ja_JP.json rename to packages/core/client/src/locale/ja-JP.json index d99cede391..b311b11e34 100644 --- a/packages/core/client/src/locale/ja_JP.json +++ b/packages/core/client/src/locale/ja-JP.json @@ -1004,5 +1004,9 @@ "Use simple pagination mode": "シンプルなページネーションモードを使用", "Set Template Engine": "テンプレートエンジンを設定", "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "ページング時にテーブルレコードの総数取得をスキップして、読み込み速度を向上させます。データ量が多い場合にこのオプションの使用をお勧めします。", - "The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "現在のユーザーにはUI設定の権限しかなく、コレクション「{{name}}」を閲覧する権限はありません。" + "The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "現在のユーザーにはUI設定の権限しかなく、コレクション「{{name}}」を閲覧する権限はありません。", + "Allow multiple selection": "複数選択を許可", + "Parent object": "親オブジェクト", + "Hide column": "列を非表示", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "設定モードでは、列全体が透明になります。非設定モードでは、列全体が非表示になります。列全体が非表示になっても、設定されたデフォルト値やその他の設定は依然として有効です。" } diff --git a/packages/core/client/src/locale/ko_KR.json b/packages/core/client/src/locale/ko-KR.json similarity index 98% rename from packages/core/client/src/locale/ko_KR.json rename to packages/core/client/src/locale/ko-KR.json index 25e3bc0ae0..f3fcf24d73 100644 --- a/packages/core/client/src/locale/ko_KR.json +++ b/packages/core/client/src/locale/ko-KR.json @@ -876,6 +876,10 @@ "Expand All": "모두 펼치기", "Clear default value": "기본값 지우기", "Open in new window": "새 창에서 열기", -"Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다.", -"Ellipsis overflow content": "생략 부호로 내용 줄임" + "Sorry, the page you visited does not exist.": "죄송합니다. 방문한 페이지가 존재하지 않습니다.", + "Allow multiple selection": "다중 선택 허용", + "Parent object": "부모 객체", + "Ellipsis overflow content": "생략 부호로 내용 줄임", + "Hide column": "열 숨기기", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "구성 모드에서는 전체 열이 투명해집니다. 비구성 모드에서는 전체 열이 숨겨집니다. 전체 열이 숨겨져도 구성된 기본값 및 기타 설정은 여전히 적용됩니다." } diff --git a/packages/core/client/src/locale/pt_BR.json b/packages/core/client/src/locale/pt-BR.json similarity index 98% rename from packages/core/client/src/locale/pt_BR.json rename to packages/core/client/src/locale/pt-BR.json index b62239943f..ba1bd69b45 100644 --- a/packages/core/client/src/locale/pt_BR.json +++ b/packages/core/client/src/locale/pt-BR.json @@ -743,5 +743,9 @@ "Clear default value": "Limpar valor padrão", "Open in new window": "Abrir em nova janela", "Sorry, the page you visited does not exist.": "Desculpe, a página que você visitou não existe.", - "Ellipsis overflow content": "Conteúdo de transbordamento com reticências" + "Allow multiple selection": "Permitir seleção múltipla", + "Parent object": "Objeto pai", + "Ellipsis overflow content": "Conteúdo de transbordamento com reticências", + "Hide column": "Ocultar coluna", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "Em modo de configuração, a coluna inteira se torna transparente. Em modo de não configuração, a coluna inteira será ocultada. Mesmo se a coluna inteira estiver oculta, seus valores padrão configurados e outras configurações ainda terão efeito." } diff --git a/packages/core/client/src/locale/ru_RU.json b/packages/core/client/src/locale/ru-RU.json similarity index 97% rename from packages/core/client/src/locale/ru_RU.json rename to packages/core/client/src/locale/ru-RU.json index 04fcd165f3..82138f9c5b 100644 --- a/packages/core/client/src/locale/ru_RU.json +++ b/packages/core/client/src/locale/ru-RU.json @@ -580,5 +580,9 @@ "Clear default value": "Очистить значение по умолчанию", "Open in new window": "Открыть в новом окне", "Sorry, the page you visited does not exist.": "Извините, посещенной вами страницы не существует.", - "Ellipsis overflow content": "Содержимое с многоточием при переполнении" + "Allow multiple selection": "Разрешить множественный выбор", + "Parent object": "Родительский объект", + "Ellipsis overflow content": "Содержимое с многоточием при переполнении", + "Hide column": "Скрыть столбец", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "В режиме конфигурации вся колонка становится прозрачной. В режиме не конфигурации вся колонка будет скрыта. Даже если вся колонка будет скрыта, её настроенные значения по умолчанию и другие настройки все равно будут действовать." } diff --git a/packages/core/client/src/locale/tr_TR.json b/packages/core/client/src/locale/tr-TR.json similarity index 97% rename from packages/core/client/src/locale/tr_TR.json rename to packages/core/client/src/locale/tr-TR.json index 60e54f3616..b6bcc27250 100644 --- a/packages/core/client/src/locale/tr_TR.json +++ b/packages/core/client/src/locale/tr-TR.json @@ -578,5 +578,9 @@ "Clear default value": "Varsayılan değeri temizle", "Open in new window": "Yeni pencerede aç", "Sorry, the page you visited does not exist.": "Üzgünüz, ziyaret ettiğiniz sayfa mevcut değil.", - "Ellipsis overflow content": "Üç nokta ile taşan içerik" + "Allow multiple selection": "Çoklu seçim izni", + "Parent object": "Üst nesne", + "Ellipsis overflow content": "Üç nokta ile taşan içerik", + "Hide column": "Sütunu gizle", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "Yapılandırma modunda, tüm sütun tamamen saydamlık alır. Yapılandırma modu olmayan durumda, tüm sütun gizlenir. Tamamen sütun gizlendiğinde bile, yapılandırılmış varsayılan değerleri ve diğer ayarları hâlâ etkin olur." } diff --git a/packages/core/client/src/locale/uk_UA.json b/packages/core/client/src/locale/uk-UA.json similarity index 98% rename from packages/core/client/src/locale/uk_UA.json rename to packages/core/client/src/locale/uk-UA.json index 2f622e7cda..c53f9816e7 100644 --- a/packages/core/client/src/locale/uk_UA.json +++ b/packages/core/client/src/locale/uk-UA.json @@ -786,5 +786,9 @@ "Clear default value": "Очистити значення за замовчуванням", "Open in new window": "Відкрити в новому вікні", "Sorry, the page you visited does not exist.": "Вибачте, сторінка, яку ви відвідали, не існує.", - "Ellipsis overflow content": "Вміст з багатокрапкою при переповненні" + "Allow multiple selection": "Дозволити множинний вибір", + "Parent object": "Батьківський об'єкт", + "Ellipsis overflow content": "Вміст з багатокрапкою при переповненні", + "Hide column": "Сховати стовпець", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "В режимі конфігурації вся колонка стає прозорою. В режимі не конфігурації вся колонка буде прихована. Якщо вся колонка буде прихована, її налаштовані значення за замовчуванням і інші налаштування все одно будуть діяти." } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 707af0105d..d6172ea7d5 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -363,7 +363,7 @@ "is empty": "为空", "is not empty": "不为空", "Edit chart": "编辑图表", - "Add text": "添加文本", + "Add Markdown": "添加 Markdown", "Filterable fields": "可筛选字段", "Edit button": "编辑按钮", "Hide": "隐藏", @@ -979,6 +979,8 @@ "The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "当前用户只有 UI 配置权限,但没有数据表 \"{{name}}\" 查看权限。", "Plugin dependency version mismatch": "插件依赖版本不一致", "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "当前插件的依赖版本与应用的版本不一致,可能无法正常工作。您确定要继续激活插件吗?", + "Allow multiple selection": "允许多选", + "Parent object": "上级对象", "Default value to current time": "设置字段默认值为当前时间", "Automatically update timestamp on update": "当记录更新时自动设置字段值为当前时间", "Default value to current server time": "设置字段默认值为当前服务端时间", @@ -1000,9 +1002,30 @@ "Are you sure you want to perform the Trigger workflow action?": "你确定执行触发工作流吗?", "Ellipsis overflow content": "省略超出长度的内容", "Picker": "选择器", - "Quarter":"季度", - "Switching the picker, the value and default value will be cleared":"切换选择器时,字段的值和默认值将会被清空", - "Stay on the current popup or page":"停留在当前弹窗或页面", - "Return to the previous popup or page":"返回上一层弹窗或页面", - "Action after successful submission":"提交成功后动作" + "Quarter": "季度", + "Switching the picker, the value and default value will be cleared": "切换选择器时,字段的值和默认值将会被清空", + "Stay on the current popup or page": "停留在当前弹窗或页面", + "Return to the previous popup or page": "返回上一层弹窗或页面", + "Action after successful submission": "提交成功后动作", + "Allow disassociation": "允许解除已有数据关联", + "Layout": "布局", + "Vertical": "垂直", + "Horizontal": "水平", + "Edit group title": "编辑分组标题", + "Title position": "标题位置", + "Dashed": "虚线", + "Left": "左", + "Center": "居中", + "Right": "右", + "Divider line color": "分割线颜色", + "Label align": "字段标题对齐方式", + "Label width": "字段标题宽度", + "When the Label exceeds the width": "字段标题超出宽度时", + "Line break": "换行", + "Ellipsis": "省略", + "Set block layout": "设置区块布局", + "Add & Update": "添加 & 更新", + "Table size":"表格大小", + "Hide column": "隐藏列", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "在配置模式下,整个列会变为透明色。在非配置模式下,整个列将被隐藏。即使整个列被隐藏了,其配置的默认值和其他设置仍然有效。" } diff --git a/packages/core/client/src/locale/zh-TW.json b/packages/core/client/src/locale/zh-TW.json index b9f1c087b5..a48d2c408e 100644 --- a/packages/core/client/src/locale/zh-TW.json +++ b/packages/core/client/src/locale/zh-TW.json @@ -876,5 +876,9 @@ "Clear default value": "清除預設值", "Open in new window": "新窗口打開", "Sorry, the page you visited does not exist.": "抱歉,你訪問的頁面不存在。", - "Ellipsis overflow content": "省略超出長度的內容" + "Allow multiple selection": "允許多選", + "Parent object": "上級物件", + "Ellipsis overflow content": "省略超出長度的內容", + "Hide column": "隱藏列", + "In configuration mode, the entire column becomes transparent. In non-configuration mode, the entire column will be hidden. Even if the entire column is hidden, its configured default values and other settings will still take effect.": "在配置模式下,整個列會變為透明色。在非配置模式下,整個列將被隱藏。即使整個列被隱藏了,其配置的默認值和其他設置仍然有效。" } diff --git a/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts b/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts index a1068fa949..048b30f3de 100644 --- a/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts +++ b/packages/core/client/src/modules/actions/__e2e__/link/basic.test.ts @@ -8,7 +8,12 @@ */ import { expect, test } from '@nocobase/test/e2e'; -import { PopupAndSubPageWithParams, oneEmptyTableWithUsers, openInNewWidow } from './templates'; +import { + PopupAndSubPageWithParams, + URLSearchParamsUseAssociationFieldValue, + oneEmptyTableWithUsers, + openInNewWidow, +} from './templates'; test.describe('Link', () => { test('basic', async ({ page, mockPage, mockRecords }) => { @@ -140,4 +145,13 @@ test.describe('Link', () => { await page.reload(); await expect(page.getByLabel('block-item-CollectionField-').getByRole('textbox')).toHaveValue('abc'); }); + + test('URL search params: use association field value', async ({ page, mockPage, mockRecords }) => { + await mockPage(URLSearchParamsUseAssociationFieldValue).goto(); + + // After clicking the Link button, the browser URL will change, and the value of the input box using variables will be updated + await page.getByLabel('action-Action.Link-Link-').click(); + await page.waitForTimeout(100); + await expect(page.getByLabel('block-item-CollectionField-')).toHaveText(`Roles:adminmemberroot`); + }); }); diff --git a/packages/core/client/src/modules/actions/__e2e__/link/templates.ts b/packages/core/client/src/modules/actions/__e2e__/link/templates.ts index 97a8ce5487..e9578e32ad 100644 --- a/packages/core/client/src/modules/actions/__e2e__/link/templates.ts +++ b/packages/core/client/src/modules/actions/__e2e__/link/templates.ts @@ -757,3 +757,317 @@ export const PopupAndSubPageWithParams = { 'x-index': 1, }, }; +export const URLSearchParamsUseAssociationFieldValue = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-app-version': '1.3.33-beta', + properties: { + gbfvfwym8ds: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-app-version': '1.3.33-beta', + properties: { + '33bst9cikf7': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + fewb4k72bc8: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + '5cfypzqvw57': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.33-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': '1xy1166yc9k', + 'x-async': false, + 'x-index': 1, + }, + '6a59q3gjjqu': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.33-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.33-beta', + properties: { + lzxiek232g3: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.33-beta', + properties: { + qhrjv5sk1tc: { + 'x-uid': 'o7zrp842yhz', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Link") }}', + 'x-action': 'customize:link', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:link', + 'x-component': 'Action.Link', + 'x-use-component-props': 'useLinkActionProps', + 'x-designer-props': { + linkageAction: true, + }, + 'x-component-props': { + url: '/admin/ids0d9esx8k', + params: [ + { + name: 'roles', + value: '{{$nRecord.roles}}', + }, + ], + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'y0m958j0dh0', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4d4wnl1b6xx', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '238yee6oghy', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'gjzmrbmobaf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'z4rsa4oitvz', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '6tec1zys03w', + 'x-async': false, + 'x-index': 1, + }, + sbdgc6nmy9x: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + '3sfuujjet76': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + p3zmu2uua0x: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'users:create', + 'x-decorator': 'FormBlockProvider', + 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'users', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + 'x-app-version': '1.3.33-beta', + properties: { + '1oq2w1ia4jy': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useCreateFormBlockProps', + 'x-app-version': '1.3.33-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + 'x-app-version': '1.3.33-beta', + properties: { + efdbxp35iht: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + k40ivzy5kcu: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + roles: { + 'x-uid': 'v8jvm1d9j8q', + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.roles', + 'x-component-props': { + fieldNames: { + label: 'name', + value: 'name', + }, + }, + 'x-app-version': '1.3.33-beta', + default: '{{$nURLSearchParams.roles}}', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '3rs4fwe2gak', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'tuqcvp6tzbg', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'dz9070niyqm', + 'x-async': false, + 'x-index': 1, + }, + '5mu8w85umxn': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'createForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': 'xgak2t61ukm', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'asfu0o75c3k', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'tgsr3gv33qk', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'pjd4g9evi3v', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hj4wq3bdtip', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '1xgpa64dn71', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ocal3pnltf2', + 'x-async': true, + 'x-index': 1, + }, + keepUid: true, + pageUid: 'ids0d9esx8k', +}; diff --git a/packages/core/client/src/modules/actions/submit/createSubmitActionSettings.tsx b/packages/core/client/src/modules/actions/submit/createSubmitActionSettings.tsx index ee1373e4e0..f45d7d1113 100644 --- a/packages/core/client/src/modules/actions/submit/createSubmitActionSettings.tsx +++ b/packages/core/client/src/modules/actions/submit/createSubmitActionSettings.tsx @@ -56,8 +56,9 @@ export function SaveMode() { const field = useField(); const fieldSchema = useFieldSchema(); const { name } = useCollection_deprecated(); - const { getEnableFieldTree, getOnLoadData } = useCollectionState(name); - + const { getEnableFieldTree, getOnLoadData } = useCollectionState(name, false, (field) => { + return ['belongsTo', 'belongsToMany', 'hasOne', 'hasMany'].includes(field.type); + }); return ( { grid: { type: 'void', 'x-component': 'Grid', - 'x-initializer': 'popup:common:addBlock', + 'x-initializer': props?.['x-initializer'] || 'popup:common:addBlock', properties: {}, }, }, diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaInitializer.test.ts index 286fc20242..6c54615bde 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/__e2e__/schemaInitializer.test.ts @@ -97,9 +97,9 @@ test.describe('configure fields', () => { page.getByLabel('block-item-CollectionField-general-details-general.manyToOne.nickname'), ).not.toBeVisible(); - // add text + // add markdown await formItemInitializer.hover(); - await page.getByRole('menuitem', { name: 'Add text' }).click(); + await page.getByRole('menuitem', { name: 'Add Markdown' }).click(); await expect(page.getByLabel('block-item-Markdown.Void-general-details')).toBeVisible(); }); }); diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx index 2bf5765b62..9653bd2d82 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-multi/detailsWithPaginationSettings.tsx @@ -23,6 +23,7 @@ import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSetti import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { setDataLoadingModeSettingsItem } from './setDataLoadingModeSettingsItem'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; const commonItems: SchemaSettingsItemType[] = [ { @@ -212,6 +213,10 @@ const commonItems: SchemaSettingsItemType[] = [ }; }, }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider', type: 'divider', diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx index 1386471f38..bd16684294 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx @@ -79,7 +79,7 @@ const commonOptions = { }, { name: 'addText', - title: '{{t("Add text")}}', + title: '{{t("Add Markdown")}}', Component: 'BlockItemInitializer', schema: { type: 'void', @@ -97,6 +97,21 @@ const commonOptions = { }, }, }, + { + name: 'addDivider', + title: '{{t("Add group")}}', + Component: 'BlockItemInitializer', + schema: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'blockSettings:divider', + 'x-component': 'Divider', + 'x-component-props': { + children: '{{t("Group")}}', + }, + }, + }, ], }; diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts index 44bd1a1628..6d7f136c3c 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/detailsBlockSettings.ts @@ -15,6 +15,8 @@ import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../. import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem'; import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; + const commonItems: SchemaSettingsItemType[] = [ { name: 'title', @@ -52,6 +54,10 @@ const commonItems: SchemaSettingsItemType[] = [ }; }, }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider', type: 'divider', diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/associationForm.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/associationForm.test.ts index 658385a48e..8fecc05759 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/associationForm.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/associationForm.test.ts @@ -50,7 +50,7 @@ test.describe('association form block', () => { await mockRecord('general'); await expect(page.getByLabel('block-item-CardItem-general-')).toBeVisible(); // 1. 打开关系字段弹窗 - await page.getByLabel('block-item-CardItem-general-').locator('a').click(); + await page.getByLabel('block-item-CardItem-general-').locator('a').first().click(); await page.getByLabel('block-item-CardItem-roles-').click(); // 2. 提交后,Table 会显示新增的数据 diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts index b7d31ab719..2f93f536e4 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts @@ -106,13 +106,13 @@ test.describe('association fields', () => { await expect(page.getByLabel('Root')).toBeVisible(); await page.getByTestId('select-object-multiple').click(); - await page.getByRole('option', { name: 'Member' }).click(); + await page.getByTitle('Member').locator('div').click(); // 再次点击,关闭下拉框。 await page.getByTestId('select-object-multiple').click(); - await expect(page.getByLabel('Admin')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Admin')).toBeVisible(); await expect(page.getByLabel('Member')).toBeHidden(); - await expect(page.getByLabel('Root')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Root')).toBeVisible(); await page.getByLabel('schema-initializer-Grid-form:configureFields-users').hover(); await page.getByRole('menuitem', { name: 'Nickname' }).click(); @@ -120,8 +120,8 @@ test.describe('association fields', () => { await page.mouse.move(200, 0); await page.waitForTimeout(200); - await expect(page.getByLabel('Admin')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Admin')).toBeVisible(); await expect(page.getByLabel('Member')).toBeHidden(); - await expect(page.getByLabel('Root')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Root')).toBeVisible(); }); }); diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts index 4ad63921c9..3a33949cbb 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts @@ -87,9 +87,9 @@ test.describe('configure fields', () => { page.getByLabel('block-item-CollectionField-general-form-general.manyToOne.nickname'), ).not.toBeVisible(); - // add text + // add markdown await page.getByLabel('schema-initializer-Grid-form:configureFields-general').hover(); - await page.getByRole('menuitem', { name: 'Text' }).click(); + await page.getByRole('menuitem', { name: 'Add Markdown' }).click(); await expect(page.getByLabel('block-item-Markdown.Void-general-form')).toBeVisible(); }); diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx index b806db3637..3a5be113e0 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/form/createFormBlockSettings.tsx @@ -20,6 +20,7 @@ import { import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem'; import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; export const createFormBlockSettings = new SchemaSettings({ name: 'blockSettings:createForm', @@ -76,6 +77,10 @@ export const createFormBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider2', type: 'divider', diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts index 7b065a877d..6b41165721 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/editFormBlockSettings.ts @@ -20,6 +20,7 @@ import { import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem'; import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; export const editFormBlockSettings = new SchemaSettings({ name: 'blockSettings:editForm', @@ -76,6 +77,10 @@ export const editFormBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider2', type: 'divider', diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx b/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx index 522a0fcaf2..fb27d8a919 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx @@ -19,7 +19,7 @@ import { useCollectionManager_deprecated, useCollection_deprecated } from '../.. import { useFieldComponentName } from '../../../../common/useFieldComponentName'; import { useCollection } from '../../../../data-source'; import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; -import { useDesignable, useValidateSchema } from '../../../../schema-component'; +import { useDesignable, useValidateSchema, useCompile } from '../../../../schema-component'; import { useIsFieldReadPretty, useIsFormReadPretty, @@ -53,6 +53,7 @@ export const fieldSettingsFormItem = new SchemaSettings({ const { dn } = useDesignable(); const field = useField(); const fieldSchema = useFieldSchema(); + const compile = useCompile(); const { getCollectionJoinField } = useCollectionManager_deprecated(); const { getField } = useCollection_deprecated(); const collectionField = @@ -75,16 +76,15 @@ export const fieldSettingsFormItem = new SchemaSettings({ }, } as ISchema, onSubmit({ title }) { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } + const result = title.trim() === '' ? collectionField?.uiSchema?.title : title; + field.title = compile(result); + fieldSchema.title = result; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + title: fieldSchema.title, + }, + }); dn.refresh(); }, }; diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/formItemInitializers.tsx b/packages/core/client/src/modules/blocks/data-blocks/form/formItemInitializers.tsx index bd0913b1c1..a0cae9c4d1 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/formItemInitializers.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/form/formItemInitializers.tsx @@ -36,9 +36,14 @@ const commonOptions = { }, { name: 'addText', - title: '{{t("Add text")}}', + title: '{{t("Add Markdown")}}', Component: 'MarkdownFormItemInitializer', }, + { + name: 'addDivider', + title: '{{t("Add group")}}', + Component: 'DividerFormItemInitializer', + }, ], }; diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useDataFormItemProps.ts b/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useDataFormItemProps.ts index b9e50ff5f0..f584b4089a 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useDataFormItemProps.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useDataFormItemProps.ts @@ -7,10 +7,18 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useForm } from '@formily/react'; +import { useCollectionRecordData } from '../../../../../data-source/collection-record/CollectionRecordProvider'; import { useSatisfiedActionValues } from '../../../../../schema-settings/LinkageRules/useActionValues'; +import { useFormBlockContext } from '../../../../../block-provider'; +import { useSubFormValue } from '../../../../../schema-component/antd/association-field/hooks'; export function useDataFormItemProps() { - const form = useForm(); - const { valueMap: style } = useSatisfiedActionValues({ category: 'style', formValues: form?.values }); + const record = useCollectionRecordData(); + const { form } = useFormBlockContext(); + const subForm = useSubFormValue(); + const { valueMap: style } = useSatisfiedActionValues({ + category: 'style', + formValues: subForm?.formValue || form?.values || record, + form, + }); return { wrapperStyle: style }; } diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/__e2e__/schemaInitializer.test.ts index 1cc78d4eec..3212167479 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/grid-card/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/__e2e__/schemaInitializer.test.ts @@ -182,11 +182,11 @@ test.describe('configure fields', () => { page.getByLabel('block-item-CollectionField-general-grid-card-general.manyToOne.nickname').first(), ).not.toBeVisible(); - // add text + // add markdown await formItemInitializer.hover(); await page.getByRole('menuitem', { name: 'ID', exact: true }).hover(); await page.mouse.wheel(0, 300); - await page.getByRole('menuitem', { name: 'Add text' }).click(); + await page.getByRole('menuitem', { name: 'Add Markdown' }).click(); await expect(page.getByLabel('block-item-Markdown.Void-general-grid-card').first()).toBeVisible(); }); diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts index f9e87c9221..074ec0f60d 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts @@ -23,6 +23,7 @@ import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettin import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem'; import { SetTheCountOfColumnsDisplayedInARow } from './SetTheCountOfColumnsDisplayedInARow'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; export const gridCardBlockSettings = new SchemaSettings({ name: 'blockSettings:gridCard', @@ -221,6 +222,10 @@ export const gridCardBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider', type: 'divider', diff --git a/packages/core/client/src/modules/blocks/data-blocks/list/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/data-blocks/list/__e2e__/schemaInitializer.test.ts index d36800be18..ea37819b0a 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/list/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/list/__e2e__/schemaInitializer.test.ts @@ -122,6 +122,7 @@ test.describe('configure item actions', () => { await page.getByLabel('schema-initializer-ActionBar-list:configureItemActions-general').first().hover(); await page.getByRole('menuitem', { name: 'Popup' }).click(); + await page.mouse.move(300, 0); await page.getByLabel('schema-initializer-ActionBar-list:configureItemActions-general').first().hover(); await page.getByRole('menuitem', { name: 'Update record' }).click(); @@ -179,9 +180,9 @@ test.describe('configure fields', () => { page.getByLabel('block-item-CollectionField-general-list-general.manyToOne.nickname').first(), ).not.toBeVisible(); - // add text + // add markdown await formItemInitializer.hover(); - await page.getByRole('menuitem', { name: 'Add text' }).click(); + await page.getByRole('menuitem', { name: 'Add Markdown' }).click(); await expect(page.getByLabel('block-item-Markdown.Void-general-list').first()).toBeVisible(); }); }); diff --git a/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts index 8c0f8d5e63..f664cf58e2 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts @@ -21,6 +21,7 @@ import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSetti import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettingsTemplate'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; import { setDataLoadingModeSettingsItem } from '../details-multi/setDataLoadingModeSettingsItem'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; export const listBlockSettings = new SchemaSettings({ name: 'blockSettings:list', @@ -224,6 +225,10 @@ export const listBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider', type: 'divider', diff --git a/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaInitializer.test.ts index 90b1336377..4da6804303 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaInitializer.test.ts @@ -87,8 +87,8 @@ test.describe('configure actions column', () => { expect(Math.floor(box.width)).toBe(width); }; - // 列宽度默认为 200 - await expectActionsColumnWidth(200); + // 列宽度默认为 100 + await expectActionsColumnWidth(100); await page.getByText('Actions', { exact: true }).hover(); await page.getByLabel('designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-users').hover(); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaSettings.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaSettings.test.ts index 73c3c514e6..f325942a51 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaSettings.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table-selector/__e2e__/schemaSettings.test.ts @@ -103,7 +103,7 @@ test.describe('table data selector schema settings', () => { await page.getByRole('menuitem', { name: 'Delete' }).hover(); await page.mouse.move(-300, 0); - await page.getByLabel('block-item-CollectionField-').nth(1).click(); + await page.getByTestId('select-data-picker').first().click(); // 3. 创建 Table 区块 await page.getByLabel('schema-initializer-Grid-popup').hover(); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/TableActionColumnInitializers.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/TableActionColumnInitializers.tsx index f353c29039..f4b7445637 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/TableActionColumnInitializers.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/TableActionColumnInitializers.tsx @@ -51,7 +51,7 @@ export const Resizable = () => { 'x-decorator': 'FormItem', 'x-component': 'InputNumber', 'x-component-props': {}, - default: fieldSchema?.['x-component-props']?.width || 200, + default: fieldSchema?.['x-component-props']?.width || 100, }, }, } as ISchema diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/TableActionInitializers.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/TableActionInitializers.tsx index c31d340ede..dba31541c6 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/TableActionInitializers.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/TableActionInitializers.tsx @@ -42,6 +42,19 @@ const commonOptions = { }, useVisible: () => useActionAvailable('create'), }, + { + type: 'item', + title: "{{t('Popup')}}", + name: 'popup', + Component: 'PopupActionInitializer', + componentProps: { + 'x-component': 'Action', + 'x-initializer': 'page:addBlock', + }, + schema: { + 'x-align': 'right', + }, + }, { type: 'item', title: "{{t('Delete')}}", diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/actions/linkage.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/actions/linkage.test.ts index 891e167c4f..78a3680d6b 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/actions/linkage.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/actions/linkage.test.ts @@ -13,14 +13,14 @@ import { T4334 } from '../templatesOfBug'; // fix https://nocobase.height.app/T-2187 test('action linkage by row data', async ({ page, mockPage }) => { await mockPage(T4334).goto(); - const adminEditAction = await page.getByLabel('action-Action.Link-Edit-update-roles-table-admin'); + const adminEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-admin'); const adminEditActionStyle = await adminEditAction.evaluate((element) => { const computedStyle = window.getComputedStyle(element); return { opacity: computedStyle.opacity, }; }); - const rootEditAction = await page.getByLabel('action-Action.Link-Edit-update-roles-table-root'); + const rootEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-root'); const rootEditActionStyle = await rootEditAction.evaluate((element) => { const computedStyle = window.getComputedStyle(element); return { @@ -29,6 +29,6 @@ test('action linkage by row data', async ({ page, mockPage }) => { }; }); - await expect(adminEditActionStyle.opacity).not.toBe('0.1'); - await expect(rootEditActionStyle.opacity).not.toBe('1'); + expect(adminEditActionStyle.opacity).not.toBe('0.1'); + expect(rootEditActionStyle.opacity).not.toBe('1'); }); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts new file mode 100644 index 0000000000..8a14b3db12 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts @@ -0,0 +1,43 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { expect, test } from '@nocobase/test/e2e'; +import { hideColumnBasic } from './templatesOfBug'; + +test.describe('hide column', () => { + test('basic', async ({ page, mockPage }) => { + await mockPage(hideColumnBasic).goto(); + + // 1. Normal table: hide column + await page.getByRole('button', { name: 'Nickname' }).hover(); + await page + .getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-users' }) + .hover(); + await page.getByRole('menuitem', { name: 'Hide column question-circle' }).click(); + await page.mouse.move(500, 0); + await page.getByLabel('block-item-CardItem-users-form').click(); + + // 2. Sub table: hide column + await page.getByRole('button', { name: 'Role name' }).hover(); + await page + .getByRole('button', { name: 'designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-roles' }) + .hover(); + await page.getByRole('menuitem', { name: 'Hide column question-circle' }).click(); + await page.mouse.move(500, 0); + + // Assert: In configuration mode, the entire column becomes transparent + await expect(page.locator('th', { hasText: 'Nickname' })).toHaveCSS('opacity', '0.3'); + await expect(page.locator('th', { hasText: 'Role name' })).toHaveCSS('opacity', '0.3'); + + // Assert: In non-configuration mode, the entire column will be hidden + await page.getByTestId('ui-editor-button').click(); + await expect(page.locator('th', { hasText: 'Nickname' })).not.toBeVisible(); + await expect(page.locator('th', { hasText: 'Role name' })).not.toBeVisible(); + }); +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaInitializer.test.ts index 1c281c1b0a..a9e93f3787 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaInitializer.test.ts @@ -321,8 +321,8 @@ test.describe('configure actions column', () => { test('column width', async ({ page, mockPage }) => { await mockPage(oneEmptyTable).goto(); - // 列宽度默认为 200 - await expect(page.getByRole('columnheader', { name: 'Actions', exact: true })).toHaveJSProperty('offsetWidth', 200); + // 列宽度默认为 100 + await expect(page.getByRole('columnheader', { name: 'Actions', exact: true })).toHaveJSProperty('offsetWidth', 100); await page.getByText('Actions', { exact: true }).hover(); await page.getByLabel('designer-schema-settings-TableV2.Column-TableV2.ActionColumnDesigner-').hover(); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaSettings.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaSettings.test.ts index cfbfd1bc41..3dfed1f5f8 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaSettings.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/schemaSettings.test.ts @@ -936,7 +936,7 @@ test.describe('actions schema settings', () => { ).toBeVisible(); }); - test('open mode', async ({ page, mockPage }) => { + test.skip('open mode', async ({ page, mockPage }) => { const nocoPage = await mockPage(testingOfOpenModeForAddChild).waitForInit(); await nocoPage.goto(); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/subTable.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/subTable.test.ts new file mode 100644 index 0000000000..320d393c30 --- /dev/null +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/subTable.test.ts @@ -0,0 +1,70 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { expect, test } from '@nocobase/test/e2e'; +import { dayjs } from '@nocobase/utils'; +import { subTableDefaultValue } from './templatesOfBug'; + +test.describe('subTable', () => { + test('defaultValue', async ({ page, mockPage }) => { + // Default values have been set as: + // staff: {{ $user }} + // timeStart: 2024-11-07 + // timeEnd: {{ $iteration.timeStart }} + await mockPage(subTableDefaultValue).goto(); + + // This is to handle timezone differences between CI environment and local environment + const expectedDateBeforeModify = dayjs('2024-11-06T16:00:00.000Z').format('YYYY-MM-DD'); + const expectedDateAfterModify = '2024-11-08'; + + // 1. Click "Add new" to add a row, default values should display correctly` + await page.getByRole('button', { name: 'Add new' }).click(); + await expect(page.getByTestId('select-object-single').getByText('Super Admin')).toBeVisible(); + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeStart-').getByPlaceholder('Select date'), + ).toHaveValue(expectedDateBeforeModify); + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeEnd-').getByPlaceholder('Select date'), + ).toHaveValue(expectedDateBeforeModify); + + // 2. Click "Add new" again to add another row, default values should display correctly + await page.getByRole('button', { name: 'Add new' }).click(); + await expect(page.getByTestId('select-object-single').getByText('Super Admin').nth(1)).toBeVisible(); + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeStart-').nth(1).getByPlaceholder('Select date'), + ).toHaveValue(expectedDateBeforeModify); + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeEnd-').nth(1).getByPlaceholder('Select date'), + ).toHaveValue(expectedDateBeforeModify); + + // 3. After modifying timeStart in both first and second rows, their corresponding timeEnd should stay synchronized + await page.getByPlaceholder('Select date').first().click(); + await page.getByRole('cell', { name: '8', exact: true }).click(); + await page.getByPlaceholder('Select date').nth(2).click(); + await page.getByRole('cell', { name: '8', exact: true }).click(); + + // First row + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeStart-').nth(0).getByPlaceholder('Select date'), + ).toHaveValue(expectedDateAfterModify); + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeEnd-').nth(0).getByPlaceholder('Select date'), + ).toHaveValue(expectedDateAfterModify); + await expect(page.getByTestId('select-object-single').getByText('Super Admin').nth(0)).toBeVisible(); + + // Second row + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeStart-').nth(1).getByPlaceholder('Select date'), + ).toHaveValue(expectedDateAfterModify); + await expect( + page.getByLabel('block-item-CollectionField-group-form-group.timeEnd-').nth(1).getByPlaceholder('Select date'), + ).toHaveValue(expectedDateAfterModify); + await expect(page.getByTestId('select-object-single').getByText('Super Admin').nth(1)).toBeVisible(); + }); +}); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts index 72ba397b28..f07c45d439 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/templatesOfBug.ts @@ -7926,3 +7926,759 @@ export const differentURL_DifferentPopupContent = { 'x-index': 1, }, }; +export const subTableDefaultValue = { + collections: [ + { + name: 'people', + fields: [ + { + name: 'group', + interface: 'o2m', + target: 'group', + targetKey: 'id', + }, + ], + }, + { + name: 'group', + fields: [ + { + name: 'staff', + interface: 'm2o', + target: 'users', + targetKey: 'id', + }, + { + name: 'timeStart', + interface: 'datetime', + }, + { + name: 'timeEnd', + interface: 'datetime', + }, + ], + }, + ], + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + gxs8yrx2r0h: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + properties: { + '823g8sd0un5': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.46-beta', + properties: { + s8gra6scztq: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.46-beta', + properties: { + k0hmoift98n: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'people:create', + 'x-decorator': 'FormBlockProvider', + 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'people', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + 'x-app-version': '1.3.46-beta', + properties: { + qj7mcan7daw: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useCreateFormBlockProps', + 'x-app-version': '1.3.46-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + 'x-app-version': '1.3.46-beta', + properties: { + zx4nzvdjpzq: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.46-beta', + properties: { + osnr82axoai: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.46-beta', + properties: { + group: { + 'x-uid': '001ix5hsr0t', + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'people.group', + 'x-component-props': { + fieldNames: { + label: 'id', + value: 'id', + }, + mode: 'SubTable', + }, + 'x-app-version': '1.3.46-beta', + default: null, + properties: { + '2go92pz3q7s': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'AssociationField.SubTable', + 'x-initializer': 'table:configureColumns', + 'x-initializer-props': { + action: false, + }, + 'x-index': 1, + 'x-app-version': '1.3.46-beta', + properties: { + '9e59ms847h0': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.3.46-beta', + properties: { + staff: { + 'x-uid': 'qfin4x5otua', + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'group.staff', + 'x-component': 'CollectionField', + 'x-component-props': { + fieldNames: { + label: 'nickname', + value: 'id', + }, + ellipsis: true, + size: 'small', + }, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.3.46-beta', + default: '{{$user}}', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'gg09b3sv8p7', + 'x-async': false, + 'x-index': 1, + }, + kd80mzx281w: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.3.46-beta', + properties: { + timeStart: { + 'x-uid': 'sm3t7czuvu1', + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'group.timeStart', + 'x-component': 'CollectionField', + 'x-component-props': {}, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.3.46-beta', + default: '2024-11-06T16:00:00.000Z', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ojxw1dbkoi0', + 'x-async': false, + 'x-index': 2, + }, + st8bezxhvax: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.3.46-beta', + properties: { + timeEnd: { + 'x-uid': '0nks9kfq38u', + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'group.timeEnd', + 'x-component': 'CollectionField', + 'x-component-props': {}, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.3.46-beta', + default: '{{$iteration.timeStart}}', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8lynj1uxlbv', + 'x-async': false, + 'x-index': 3, + }, + }, + 'x-uid': 'bsd9l4pl5by', + 'x-async': false, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 't9bhhahpuln', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'm94ti0kcqqh', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9po3njajeii', + 'x-async': false, + 'x-index': 1, + }, + gr2fjadfkef: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'createForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + }, + 'x-app-version': '1.3.46-beta', + 'x-uid': 'v5zhsmloels', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'o9fcw2oo6vi', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7361xvr7amv', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'an5bhgxdio8', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '0hixaaou1xh', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'nfot2l93mkp', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '3lfdt8s5cay', + 'x-async': true, + 'x-index': 1, + }, +}; +export const hideColumnBasic = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-app-version': '1.4.0-alpha.1', + properties: { + bfc254m95nt: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-app-version': '1.4.0-alpha.1', + properties: { + '4e61hstsu6e': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.4.0-alpha.1', + properties: { + j9xovyw5nce: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.4.0-alpha.1', + properties: { + '9ivpaiunf9y': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.4.0-alpha.1', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '51wc0e6u31j', + 'x-async': false, + 'x-index': 1, + }, + bs7p8uu830m: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.4.0-alpha.1', + properties: { + jw80rplhox4: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': 'u06c9tleqou', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9s1pfakz0om', + 'x-async': false, + 'x-index': 1, + }, + aph2w5uiev9: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + nickname: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'users.nickname', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-read-pretty': true, + 'x-decorator': null, + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '3w3zou3ceb3', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4m9sepacwvm', + 'x-async': false, + 'x-index': 2, + }, + wpmysa656gq: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + username: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'users.username', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-read-pretty': true, + 'x-decorator': null, + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '38dn4mm3hr5', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9ed6nyhlgzi', + 'x-async': false, + 'x-index': 3, + }, + }, + 'x-uid': 'sc4a347zrvr', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'z8gp6wsukkv', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '60rzu0t7mm9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'po4tzdbip1j', + 'x-async': false, + 'x-index': 1, + }, + h9rg0fz8izl: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.4.0-alpha.1', + properties: { + acu1j5bgeeh: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.4.0-alpha.1', + properties: { + azoaet9funq: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'users:create', + 'x-decorator': 'FormBlockProvider', + 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'users', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + 'x-app-version': '1.4.0-alpha.1', + properties: { + pfltqojjolr: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useCreateFormBlockProps', + 'x-app-version': '1.4.0-alpha.1', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + 'x-app-version': '1.4.0-alpha.1', + properties: { + y4g2q6tyb0a: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.4.0-alpha.1', + properties: { + thgbnha840e: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.4.0-alpha.1', + properties: { + roles: { + 'x-uid': 'ivyzmc9lc21', + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.roles', + 'x-component-props': { + fieldNames: { + label: 'name', + value: 'name', + }, + mode: 'SubTable', + }, + 'x-app-version': '1.4.0-alpha.1', + default: null, + properties: { + eoizayhit92: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'AssociationField.SubTable', + 'x-initializer': 'table:configureColumns', + 'x-initializer-props': { + action: false, + }, + 'x-index': 1, + 'x-app-version': '1.4.0-alpha.1', + properties: { + nhe3d7uuyur: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + name: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'roles.name', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': 'e89fxf1mg7i', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kxbrpz5jmc7', + 'x-async': false, + 'x-index': 1, + }, + jgnjc800er5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.4.0-alpha.1', + properties: { + title: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'roles.title', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': 'bankqu2es91', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'uwyyk7ix2lb', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'ff16rxs8lbo', + 'x-async': false, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4nagabglobc', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'noqp2jcfp2j', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'a330n90aoyf', + 'x-async': false, + 'x-index': 1, + }, + vpwq72gj933: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'createForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + }, + 'x-app-version': '1.4.0-alpha.1', + 'x-uid': '1nauytag1dh', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '342ktwn88zo', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '84tmhiuph4u', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'j70955c17aq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'vtz6tq8n0mx', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '1f4xbt4prbg', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ifb5nqtgnng', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx index f9ab661981..e07bb03f87 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/hooks/useTableBlockProps.tsx @@ -10,7 +10,7 @@ import { ArrayField } from '@formily/core'; import { useField, useFieldSchema } from '@formily/react'; import { isEqual } from 'lodash'; -import { useCallback, useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useRef } from 'react'; import { useTableBlockContext } from '../../../../../block-provider/TableBlockProvider'; import { findFilterTargets } from '../../../../../block-provider/hooks'; import { DataBlock, useFilterBlock } from '../../../../../filter-provider/FilterProvider'; @@ -21,10 +21,12 @@ export const useTableBlockProps = () => { const field = useField(); const fieldSchema = useFieldSchema(); const ctx = useTableBlockContext(); - const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort']; const { getDataBlocks } = useFilterBlock(); const isLoading = ctx?.service?.loading; - const params = useMemo(() => ctx?.service?.params, [JSON.stringify(ctx?.service?.params)]); + + const ctxRef = useRef(null); + ctxRef.current = ctx; + useEffect(() => { if (!isLoading) { const serviceResponse = ctx?.service?.data; @@ -78,19 +80,20 @@ export const useTableBlockProps = () => { ), onChange: useCallback( ({ current, pageSize }, filters, sorter) => { + const globalSort = fieldSchema.parent?.['x-decorator-props']?.['params']?.['sort']; const sort = sorter.order ? sorter.order === `ascend` ? [sorter.field] : [`-${sorter.field}`] - : globalSort || ctx.dragSortBy; + : globalSort || ctxRef.current.dragSortBy; const currentPageSize = pageSize || fieldSchema.parent?.['x-decorator-props']?.['params']?.pageSize; - const args = { ...params?.[0], page: current || 1, pageSize: currentPageSize }; + const args = { ...ctxRef.current?.service?.params?.[0], page: current || 1, pageSize: currentPageSize }; if (sort) { args['sort'] = sort; } - ctx.service.run(args); + ctxRef.current?.service.run(args); }, - [globalSort, params, ctx.dragSort], + [fieldSchema.parent], ), onClickRow: useCallback( (record, setSelectedRow, selectedRow) => { diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx index ec735aa1ad..61ad092fce 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx @@ -187,6 +187,40 @@ export const tableBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'tableSize', + type: 'select', + useComponentProps() { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + return { + title: t('Table size'), + value: field.componentProps?.size || 'middle', + options: [ + { label: t('Large'), value: 'large' }, + { label: t('Middle'), value: 'middle' }, + { label: t('Small'), value: 'small' }, + ], + onChange: (size) => { + const schema = fieldSchema.reduceProperties((_, s) => { + if (s['x-component'] === 'TableV2') { + return s; + } + }, null); + schema['x-component-props'] = schema['x-component-props'] || {}; + schema['x-component-props']['size'] = size; + dn.emit('patch', { + schema: { + ['x-uid']: schema['x-uid'], + 'x-decorator-props': schema['x-component-props'], + }, + }); + }, + }; + }, + }, { name: 'ConnectDataBlocks', Component: SchemaSettingsConnectDataBlocks, @@ -201,6 +235,7 @@ export const tableBlockSettings = new SchemaSettings({ { name: 'divider', type: 'divider', + sort: 7000, useVisible: () => { const fieldSchema = useFieldSchema(); const supportTemplate = !fieldSchema?.['x-decorator-props']?.disableTemplate; @@ -209,6 +244,7 @@ export const tableBlockSettings = new SchemaSettings({ }, { name: 'ConvertReferenceToDuplicate', + sort: 8000, Component: SchemaSettingsTemplate, useComponentProps() { const { name } = useCollection_deprecated(); @@ -231,10 +267,12 @@ export const tableBlockSettings = new SchemaSettings({ { name: 'divider2', type: 'divider', + sort: 9000, }, { name: 'delete', type: 'remove', + sort: 10000, useComponentProps: () => { return { removeParentsIfNoChildren: true, diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx index d6b01c61d2..2ce8f8fa50 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/tableColumnSettings.tsx @@ -7,8 +7,10 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { QuestionCircleOutlined } from '@ant-design/icons'; import { ISchema } from '@formily/json-schema'; import { useField, useFieldSchema } from '@formily/react'; +import { Tooltip } from 'antd'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useApp, useSchemaToolbar } from '../../../../application'; @@ -66,16 +68,15 @@ export const tableColumnSettings = new SchemaSettings({ }, } as ISchema, onSubmit: ({ title }) => { - if (title) { - field.title = title; - columnSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': columnSchema['x-uid'], - title: columnSchema.title, - }, - }); - } + field.title = title; + columnSchema.title = title; + dn.emit('patch', { + schema: { + 'x-uid': columnSchema['x-uid'], + title: columnSchema.title, + }, + }); + dn.refresh(); }, }; @@ -121,7 +122,7 @@ export const tableColumnSettings = new SchemaSettings({ title: t('Column width'), properties: { width: { - default: columnSchema?.['x-component-props']?.['width'] || 200, + default: columnSchema?.['x-component-props']?.['width'] || 100, 'x-decorator': 'FormItem', 'x-component': 'InputNumber', 'x-component-props': {}, @@ -381,6 +382,47 @@ export const tableColumnSettings = new SchemaSettings({ }; }, }, + { + name: 'hidden', + type: 'switch', + useComponentProps() { + const field: any = useField(); + const { t } = useTranslation(); + const columnSchema = useFieldSchema(); + const { dn } = useDesignable(); + + return { + title: ( + + {t('Hide column')} + + + + + ), + checked: field.componentProps.columnHidden, + onChange: (v) => { + const schema: ISchema = { + ['x-uid']: columnSchema['x-uid'], + }; + columnSchema['x-component-props'] = { + ...columnSchema['x-component-props'], + columnHidden: v, + }; + schema['x-component-props'] = columnSchema['x-component-props']; + field.componentProps.columnHidden = v; + dn.emit('patch', { + schema, + }); + dn.refresh(); + }, + }; + }, + }, fieldComponentSettingsItem, ], }, diff --git a/packages/core/client/src/modules/blocks/filter-blocks/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/filter-blocks/__e2e__/schemaInitializer.test.ts index 92ad32b637..b92340de78 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/__e2e__/schemaInitializer.test.ts @@ -64,7 +64,7 @@ test.describe('where filter block can be added', () => { // filter await page.getByLabel('action-Action-Filter-submit-').click({ position: { x: 10, y: 10 } }); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); for (const record of newUserRecords) { await expect(page.getByLabel('block-item-CardItem-users-table').getByText(record.nickname)).toBeVisible({ visible: record === newUserRecords[0], @@ -82,7 +82,7 @@ test.describe('where filter block can be added', () => { // reset await page.getByLabel('action-Action-Reset-users-').click({ position: { x: 10, y: 10 } }); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); for (const record of newUserRecords) { await expect(page.getByLabel('block-item-CardItem-users-table').getByText(record.nickname)).toBeVisible(); await expect(page.getByLabel('block-item-CardItem-users-list').getByText(record.nickname)).toBeVisible(); @@ -123,7 +123,7 @@ test.describe('where filter block can be added', () => { .getByRole('textbox') .fill(usersRecords[0].roles[0].name); await page.getByLabel('action-Action-Filter-submit-').click({ position: { x: 10, y: 10 } }); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); for (const record of usersRecords[0].roles) { await expect(page.getByLabel('block-item-CardItem-roles-details').getByText(record.name)).toBeVisible({ @@ -141,7 +141,7 @@ test.describe('where filter block can be added', () => { } await page.getByLabel('action-Action-Reset-roles-').click({ position: { x: 10, y: 10 } }); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); for (const record of usersRecords[0].roles) { await expect(page.getByLabel('block-item-CardItem-roles-table').getByText(record.name)).toBeVisible(); await expect(page.getByLabel('block-item-CardItem-roles-list').getByText(record.name)).toBeVisible(); @@ -172,7 +172,7 @@ test.describe('where filter block can be added', () => { await page.getByLabel('block-item-CardItem-users-filter-form').getByRole('textbox').fill(usersRecords[0].nickname); await page.getByLabel('action-Action-Filter-submit-users-filter-form').click({ position: { x: 10, y: 10 } }); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); for (const record of usersRecords) { await expect(page.getByLabel('block-item-CardItem-users-details').getByText(record.nickname)).toBeVisible({ visible: record === usersRecords[0], @@ -194,7 +194,7 @@ test.describe('where filter block can be added', () => { } await page.getByLabel('action-Action-Reset-users-').click({ position: { x: 10, y: 10 } }); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); for (const record of usersRecords) { await expect( page diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts index 74e4ec18e4..f4bb51468d 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts @@ -75,9 +75,9 @@ test.describe('configure fields', () => { page.getByLabel('block-item-CollectionField-general-filter-form-general.manyToOne.nickname'), ).not.toBeVisible(); - // add text + // add markdown await formItemInitializer.hover(); - await page.getByRole('menuitem', { name: 'Add text' }).click(); + await page.getByRole('menuitem', { name: 'Add Markdown' }).click(); await expect(page.getByLabel('block-item-Markdown.Void-general-filter-form')).toBeVisible(); }); diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts index b0bf10d376..64573a1b0f 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormBlockSettings.ts @@ -18,6 +18,7 @@ import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/Schem import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem'; import { SchemaSettingsConnectDataBlocks } from '../../../../schema-settings/SchemaSettingsConnectDataBlocks'; import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; export const filterFormBlockSettings = new SchemaSettings({ name: 'blockSettings:filterForm', @@ -67,6 +68,10 @@ export const filterFormBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider', type: 'divider', diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts index 1d3624f8a6..838f92c3ab 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemFieldSettings.ts @@ -16,9 +16,16 @@ import { useApp } from '../../../../application'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../../collection-manager'; import { useFieldComponentName } from '../../../../common/useFieldComponentName'; -import { EditOperator, useDesignable, useValidateSchema } from '../../../../schema-component'; -import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue'; import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; +import { EditOperator, useDesignable, useValidateSchema, useCompile } from '../../../../schema-component'; +import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue'; + +const fieldComponentNameMap = (name: string) => { + if (name === 'Select') { + return 'FilterSelect'; + } + return name; +}; export const filterFormItemFieldSettings = new SchemaSettings({ name: 'fieldSettings:FilterFormItem', @@ -42,6 +49,7 @@ export const filterFormItemFieldSettings = new SchemaSettings({ const { t } = useTranslation(); const { dn } = useDesignable(); const field = useField(); + const compile = useCompile(); const fieldSchema = useFieldSchema(); const { getCollectionJoinField } = useCollectionManager_deprecated(); const { getField } = useCollection_deprecated(); @@ -65,16 +73,16 @@ export const filterFormItemFieldSettings = new SchemaSettings({ }, } as ISchema, onSubmit({ title }) { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } + const result = title.trim() === '' ? collectionField?.uiSchema?.title : title; + field.title = compile(result); + fieldSchema.title = result; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + title: fieldSchema.title, + }, + }); + dn.refresh(); }, }; @@ -347,7 +355,9 @@ export const filterFormItemFieldSettings = new SchemaSettings({ useChildren() { const app = useApp(); const fieldComponentName = useFieldComponentName(); - const componentSettings = app.schemaSettingsManager.get(`fieldSettings:component:${fieldComponentName}`); + const componentSettings = app.schemaSettingsManager.get( + `fieldSettings:component:${fieldComponentNameMap(fieldComponentName)}`, + ); return componentSettings?.items || []; }, }, diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemInitializers.tsx b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemInitializers.tsx index 63ab0e958e..494d27f238 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemInitializers.tsx +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/filterFormItemInitializers.tsx @@ -38,7 +38,7 @@ const commonOptions = { type: 'divider', }, { - title: '{{t("Add text")}}', + title: '{{t("Add Markdown")}}', Component: 'MarkdownFormItemInitializer', name: 'addText', }, diff --git a/packages/core/client/src/modules/blocks/index.ts b/packages/core/client/src/modules/blocks/index.ts index 6266fcd2ee..5869ff15c3 100644 --- a/packages/core/client/src/modules/blocks/index.ts +++ b/packages/core/client/src/modules/blocks/index.ts @@ -10,3 +10,4 @@ export * from './data-blocks/details-multi'; export * from './data-blocks/details-single'; export * from './useActionAvailable'; +export * from './useSourceId'; diff --git a/packages/core/client/src/modules/blocks/other-blocks/divider/DividerFormItemInitializer.tsx b/packages/core/client/src/modules/blocks/other-blocks/divider/DividerFormItemInitializer.tsx new file mode 100644 index 0000000000..eb50a9bcd6 --- /dev/null +++ b/packages/core/client/src/modules/blocks/other-blocks/divider/DividerFormItemInitializer.tsx @@ -0,0 +1,35 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { LineOutlined } from '@ant-design/icons'; +import React from 'react'; +import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../../../application'; + +export const DividerFormItemInitializer = () => { + const { insert } = useSchemaInitializer(); + const itemConfig = useSchemaInitializerItem(); + return ( + } + onClick={() => { + insert({ + type: 'void', + 'x-decorator': 'FormItem', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'blockSettings:divider', + 'x-component': 'Divider', + 'x-component-props': { + children: '{{t("Group")}}', + }, + }); + }} + /> + ); +}; diff --git a/packages/core/client/src/modules/blocks/other-blocks/divider/dividerSettings.tsx b/packages/core/client/src/modules/blocks/other-blocks/divider/dividerSettings.tsx new file mode 100644 index 0000000000..12e5bd9766 --- /dev/null +++ b/packages/core/client/src/modules/blocks/other-blocks/divider/dividerSettings.tsx @@ -0,0 +1,202 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useField, useFieldSchema } from '@formily/react'; +import { useTranslation } from 'react-i18next'; +import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; +import { SchemaSettingsModalItem } from '../../../../schema-settings'; + +import { useDesignable } from '../../../../schema-component/hooks/useDesignable'; +import { ColorPicker } from '../../../../schema-component'; +import React from 'react'; + +export function GroupTitleEditor(props) { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn } = useDesignable(); + const { t } = useTranslation(); + + return ( + { + field.componentProps.children = children; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].children = children; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-component-props': { + ...fieldSchema['x-component-props'], + }, + }, + }); + dn.refresh(); + }} + /> + ); +} + +export const dividerSettings = new SchemaSettings({ + name: 'blockSettings:divider', + items: [ + { + name: 'editTitle', + type: 'item', + Component: GroupTitleEditor, + }, + { + name: 'orientation', + type: 'select', + useComponentProps() { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + return { + title: t('Title position'), + value: field.componentProps?.orientation || 'left', + options: [ + { label: t('Left'), value: 'left' }, + { label: t('Center'), value: 'center' }, + { label: t('Right'), value: 'right' }, + ], + onChange: (orientation) => { + field.componentProps = field.componentProps || {}; + field.componentProps.orientation = orientation; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props']['orientation'] = orientation; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-component-props': fieldSchema['x-component-props'], + }, + }); + }, + }; + }, + }, + { + name: 'dashed', + type: 'switch', + useComponentProps: () => { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + + return { + title: t('Dashed'), + defaultChecked: true, + checked: field.componentProps.dashed, + onChange: (flag) => { + field.componentProps.dashed = flag; + fieldSchema['x-component-props'].dashed = flag; + if (flag === false) { + fieldSchema['x-component-props'].dashed = false; + } + dn.emit('patch', { + schema: fieldSchema, + }); + dn.refresh(); + }, + }; + }, + }, + { + name: 'color', + type: 'item', + useComponentProps: () => { + const { t } = useTranslation(); + const { dn } = useDesignable(); + const field = useField(); + const fieldSchema = useFieldSchema(); + return { + title: ( +
+ {t('Color')} +
+ { + field.componentProps = field.componentProps || {}; + field.componentProps.color = value; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].color = value; + dn.emit('patch', { + schema: fieldSchema, + }); + dn.refresh(); + }} + /> +
+
+ ), + }; + }, + }, + { + name: 'borderColor', + type: 'item', + useComponentProps: () => { + const { t } = useTranslation(); + const { dn } = useDesignable(); + const field = useField(); + const fieldSchema = useFieldSchema(); + return { + title: ( +
+ {t('Divider line color')} +
+ { + field.componentProps = field.componentProps || {}; + field.componentProps.borderColor = value; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].borderColor = value; + dn.emit('patch', { + schema: fieldSchema, + }); + dn.refresh(); + }} + /> +
+
+ ), + }; + }, + }, + { + name: 'delete', + type: 'remove', + useComponentProps() { + return { + removeParentsIfNoChildren: true, + breakRemoveOn: { + 'x-component': 'Grid', + }, + }; + }, + }, + ] as any, +}); diff --git a/packages/core/client/src/modules/fields/__e2e__/component/Input.Preview/basic.test.ts b/packages/core/client/src/modules/fields/__e2e__/component/Input.Preview/basic.test.ts index efa3dce47c..e893f2d0f5 100644 --- a/packages/core/client/src/modules/fields/__e2e__/component/Input.Preview/basic.test.ts +++ b/packages/core/client/src/modules/fields/__e2e__/component/Input.Preview/basic.test.ts @@ -32,7 +32,12 @@ test.describe('Input.Preview', () => { // 4. 切换图片大小到 Large,大小切换正常 await page.getByLabel('block-item-CollectionField-').hover(); await page.getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-general-general').hover(); - await page.getByRole('menuitem', { name: 'Size Small' }).click(); + await page.getByRole('menuitem', { name: 'Size Small' }).click({ + position: { + x: 160, + y: 10, + }, + }); await page.getByRole('option', { name: 'Large' }).click(); await expect(page.getByLabel('block-item-CollectionField-').getByRole('img').first()).toHaveJSProperty('width', 72); diff --git a/packages/core/client/src/modules/fields/component/FileManager/fileManagerComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/FileManager/fileManagerComponentFieldSettings.tsx index 16d3f1dcb4..17841a7c98 100644 --- a/packages/core/client/src/modules/fields/component/FileManager/fileManagerComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/FileManager/fileManagerComponentFieldSettings.tsx @@ -15,12 +15,12 @@ import { useFieldComponentName } from '../../../../common/useFieldComponentName' import { useDesignable, useFieldModeOptions, useIsAddNewForm } from '../../../../schema-component'; import { isSubMode } from '../../../../schema-component/antd/association-field/util'; import { - useIsFieldReadPretty, useIsAssociationField, + useIsFieldReadPretty, } from '../../../../schema-component/antd/form-item/FormItem.Settings'; import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; -import { allowMultiple } from '../Select/selectComponentFieldSettings'; import { useIsShowMultipleSwitch } from '../../../../schema-settings/hooks/useIsShowMultipleSwitch'; +import { getAllowMultiple } from '../Select/selectComponentFieldSettings'; const fieldComponent: any = { name: 'fieldComponent', @@ -183,7 +183,7 @@ export const fileManagerComponentFieldSettings = new SchemaSettings({ }, }, { - ...allowMultiple, + ...getAllowMultiple(), useVisible() { const isAssociationField = useIsAssociationField(); const IsShowMultipleSwitch = useIsShowMultipleSwitch(); diff --git a/packages/core/client/src/modules/fields/component/Input/inputComponentSettings.tsx b/packages/core/client/src/modules/fields/component/Input/inputComponentSettings.tsx index b07da43354..c2e33a5c8c 100644 --- a/packages/core/client/src/modules/fields/component/Input/inputComponentSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Input/inputComponentSettings.tsx @@ -11,14 +11,18 @@ import { useField, useFieldSchema } from '@formily/react'; import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { SchemaSettingsItemType } from '../../../../application/schema-settings/types'; -import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; +import { + useColumnSchema, + useTableFieldInstanceList, +} from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; import { useDesignable } from '../../../../schema-component/hooks/useDesignable'; export const ellipsisSettingsItem: SchemaSettingsItemType = { name: 'ellipsis', type: 'switch', useComponentProps() { - const { fieldSchema: tableFieldSchema, filedInstanceList } = useColumnSchema(); + const { fieldSchema: tableFieldSchema } = useColumnSchema(); + const tableFieldInstanceList = useTableFieldInstanceList(); const fieldSchema = useFieldSchema(); const formField = useField(); const { dn } = useDesignable(); @@ -26,8 +30,8 @@ export const ellipsisSettingsItem: SchemaSettingsItemType = { const schema = tableFieldSchema || fieldSchema; const hidden = tableFieldSchema - ? filedInstanceList[0] - ? !filedInstanceList[0].readPretty + ? tableFieldInstanceList[0] + ? !tableFieldInstanceList[0].readPretty : !tableFieldSchema['x-read-pretty'] : !formField.readPretty; @@ -46,10 +50,15 @@ export const ellipsisSettingsItem: SchemaSettingsItemType = { }, }); - if (tableFieldSchema && filedInstanceList) { - filedInstanceList.forEach((fieldInstance) => { + if (tableFieldSchema && tableFieldInstanceList) { + tableFieldInstanceList.forEach((fieldInstance) => { fieldInstance.componentProps.ellipsis = checked; }); + schema['x-component-props']['ellipsis'] = checked; + const path = formField.path?.splice(formField.path?.length - 1, 1); + formField.form.query(`${path.concat(`*.` + fieldSchema.name)}`).forEach((f) => { + f.componentProps.ellipsis = checked; + }); } else { formField.componentProps.ellipsis = checked; } diff --git a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx index 8540f64156..4f25315cd5 100644 --- a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx @@ -21,6 +21,7 @@ import { useIsFormReadPretty, } from '../../../../schema-component/antd/form-item/FormItem.Settings'; import { linkageRules, setDefaultSortingRules } from '../SubTable/subTablePopoverComponentFieldSettings'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; const allowMultiple: any = { name: 'allowMultiple', @@ -143,5 +144,9 @@ export const subformComponentFieldSettings = new SchemaSettings({ }, setDefaultSortingRules, linkageRules, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, ], }); diff --git a/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx index 2488649c78..39eb9cd68d 100644 --- a/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Picker/recordPickerComponentFieldSettings.tsx @@ -15,55 +15,9 @@ import { useFieldComponentName } from '../../../../common/useFieldComponentName' import { useCollectionField } from '../../../../data-source'; import { useDesignable, useFieldModeOptions, useIsAddNewForm } from '../../../../schema-component'; import { isSubMode } from '../../../../schema-component/antd/association-field/util'; -import { - allowAddNew, - useIsFieldReadPretty, - useTitleFieldOptions, -} from '../../../../schema-component/antd/form-item/FormItem.Settings'; +import { allowAddNew, useIsFieldReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings'; import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; - -export const titleField: any = { - name: 'titleField', - type: 'select', - useComponentProps() { - const { t } = useTranslation(); - const field = useField(); - const { uiSchema, fieldSchema: tableColumnSchema, collectionField: tableColumnField } = useColumnSchema(); - const options = useTitleFieldOptions(); - const schema = useFieldSchema(); - const fieldSchema = tableColumnSchema || schema; - const targetCollectionField = useCollectionField(); - const collectionField = tableColumnField || targetCollectionField; - const { dn } = useDesignable(); - const fieldNames = - field?.componentProps?.fieldNames || - fieldSchema?.['x-component-props']?.['fieldNames'] || - uiSchema?.['x-component-props']?.['fieldNames']; - return { - title: t('Title field'), - options, - value: fieldNames?.label, - onChange(label) { - const schema = { - ['x-uid']: fieldSchema['x-uid'], - }; - const fieldNames = { - ...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'], - ...field.componentProps.fieldNames, - label, - }; - fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; - fieldSchema['x-component-props']['fieldNames'] = fieldNames; - schema['x-component-props'] = fieldSchema['x-component-props']; - field.componentProps.fieldNames = fieldSchema['x-component-props'].fieldNames; - dn.emit('patch', { - schema, - }); - dn.refresh(); - }, - }; - }, -}; +import { titleField } from '../Select/selectComponentFieldSettings'; const allowMultiple: any = { name: 'allowMultiple', diff --git a/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx index d7c7e616ef..bf368ff7f7 100644 --- a/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx @@ -17,8 +17,10 @@ import { useDesignable, useFieldModeOptions, useIsAddNewForm } from '../../../.. import { isSubMode } from '../../../../schema-component/antd/association-field/util'; import { useIsFieldReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings'; import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; -import { titleField } from '../Picker/recordPickerComponentFieldSettings'; +import { titleField } from '../Select/selectComponentFieldSettings'; + import { linkageRules } from '../SubTable/subTablePopoverComponentFieldSettings'; +import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem'; const allowMultiple: any = { name: 'allowMultiple', @@ -104,5 +106,14 @@ const fieldComponent: any = { export const subformPopoverComponentFieldSettings = new SchemaSettings({ name: 'fieldSettings:component:PopoverNester', - items: [fieldComponent, allowMultiple, titleField, linkageRules], + items: [ + fieldComponent, + allowMultiple, + titleField, + linkageRules, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, + ], }); diff --git a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx index df9ea2847f..52fcb1189f 100644 --- a/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Select/selectComponentFieldSettings.tsx @@ -73,7 +73,7 @@ const enableLink = { }, }; -const titleField: any = { +export const titleField: any = { name: 'titleField', type: 'select', useComponentProps() { @@ -121,43 +121,42 @@ const titleField: any = { }, }; -export const allowMultiple: any = { - name: 'allowMultiple', - type: 'switch', - useVisible() { - const isFieldReadPretty = useIsFieldReadPretty(); - const collectionField = useCollectionField(); - return !isFieldReadPretty && ['hasMany', 'belongsToMany'].includes(collectionField?.type); - }, - useComponentProps() { - const { t } = useTranslation(); - const field = useField(); - const { fieldSchema: tableColumnSchema } = useColumnSchema(); - const schema = useFieldSchema(); - const fieldSchema = tableColumnSchema || schema; - const { dn, refresh } = useDesignable(); - return { - title: t('Allow multiple'), - checked: - fieldSchema['x-component-props']?.multiple === undefined ? true : fieldSchema['x-component-props'].multiple, - onChange(value) { - const schema = { - ['x-uid']: fieldSchema['x-uid'], - }; - fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; - field.componentProps = field.componentProps || {}; +export const getAllowMultiple = (params?: { title: string }) => { + const title = params?.title || 'Allow multiple'; - fieldSchema['x-component-props'].multiple = value; - field.componentProps.multiple = value; + return { + name: 'allowMultiple', + type: 'switch', + useComponentProps() { + const { t } = useTranslation(); + const field = useField(); + const { fieldSchema: tableColumnSchema } = useColumnSchema(); + const schema = useFieldSchema(); + const fieldSchema = tableColumnSchema || schema; + const { dn, refresh } = useDesignable(); + return { + title: t(title), + checked: + fieldSchema['x-component-props']?.multiple === undefined ? true : fieldSchema['x-component-props'].multiple, + onChange(value) { + const schema = { + ['x-uid']: fieldSchema['x-uid'], + }; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + field.componentProps = field.componentProps || {}; - schema['x-component-props'] = fieldSchema['x-component-props']; - dn.emit('patch', { - schema, - }); - refresh(); - }, - }; - }, + fieldSchema['x-component-props'].multiple = value; + field.componentProps.multiple = value; + + schema['x-component-props'] = fieldSchema['x-component-props']; + dn.emit('patch', { + schema, + }); + refresh(); + }, + }; + }, + }; }; const quickCreate: any = { @@ -364,7 +363,7 @@ export const selectComponentFieldSettings = new SchemaSettings({ }, }, { - ...allowMultiple, + ...getAllowMultiple(), useVisible() { const isFieldReadPretty = useIsFieldReadPretty(); const isAssociationField = useIsAssociationField(); @@ -385,3 +384,50 @@ export const selectComponentFieldSettings = new SchemaSettings({ }, ], }); + +/** + * Used for Select fields in filter form blocks + */ +export const filterSelectComponentFieldSettings = new SchemaSettings({ + name: 'fieldSettings:component:FilterSelect', + items: [ + { + ...fieldComponent, + useVisible: useIsAssociationField, + }, + { + ...setTheDataScope, + useVisible() { + const isSelectFieldMode = useIsSelectFieldMode(); + const isFieldReadPretty = useIsFieldReadPretty(); + return isSelectFieldMode && !isFieldReadPretty; + }, + }, + { + ...setDefaultSortingRules, + useComponentProps() { + const { fieldSchema } = useColumnSchema(); + return { + fieldSchema, + }; + }, + useVisible() { + const isSelectFieldMode = useIsSelectFieldMode(); + const isFieldReadPretty = useIsFieldReadPretty(); + return isSelectFieldMode && !isFieldReadPretty; + }, + }, + getAllowMultiple({ title: 'Allow multiple selection' }), + { + ...titleField, + useVisible: useIsAssociationField, + }, + { + ...enableLink, + useVisible() { + const readPretty = useIsFieldReadPretty(); + return useIsAssociationField() && readPretty; + }, + }, + ], +}); diff --git a/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx index 06339a4bd5..a2c60dd972 100644 --- a/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx @@ -104,6 +104,38 @@ const allowSelectExistingRecord = { }, }; +const allowDisassociation = { + name: 'allowDisassociation', + type: 'switch', + useVisible() { + const readPretty = useIsFieldReadPretty(); + return !readPretty; + }, + useComponentProps() { + const { t } = useTranslation(); + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn, refresh } = useDesignable(); + return { + title: t('Allow disassociation'), + checked: fieldSchema['x-component-props']?.allowDisassociation !== false, + onChange(value) { + const schema = { + ['x-uid']: fieldSchema['x-uid'], + }; + field.componentProps.allowDisassociation = value; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].allowDisassociation = value; + schema['x-component-props'] = fieldSchema['x-component-props']; + dn.emit('patch', { + schema, + }); + refresh(); + }, + }; + }, +}; + export const setDefaultSortingRules = { name: 'SetDefaultSortingRules', type: 'modal', @@ -291,7 +323,46 @@ export const linkageRules = { }, }; +export const recordPerPage = { + name: 'recordsPerPage', + type: 'select', + useComponentProps() { + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const { dn } = useDesignable(); + const pageSizeOptions = [10, 20, 50, 100]; + + return { + title: t('Records per page'), + value: field.componentProps?.pageSize || 10, + options: pageSizeOptions.map((v) => ({ value: v })), + onChange: (pageSize) => { + const schema = { + ['x-uid']: fieldSchema['x-uid'], + }; + field.componentProps = field.componentProps || {}; + field.componentProps.pageSize = pageSize; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].pageSize = pageSize; + schema['x-component-props'] = fieldSchema['x-component-props']; + dn.emit('patch', { + schema, + }); + }, + }; + }, +}; + export const subTablePopoverComponentFieldSettings = new SchemaSettings({ name: 'fieldSettings:component:SubTable', - items: [fieldComponent, allowAddNewData, allowSelectExistingRecord, setDefaultSortingRules, linkageRules], + items: [ + fieldComponent, + allowAddNewData, + allowSelectExistingRecord, + allowDisassociation, + setDefaultSortingRules, + linkageRules, + recordPerPage, + ], }); diff --git a/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts index 8da21efcf6..ceb401c7ae 100644 --- a/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/menu/__e2e__/schemaInitializer.test.ts @@ -102,7 +102,7 @@ test.describe('add menu item', () => { // open link page await page.getByLabel(pageLink).click(); - await page.waitForTimeout(1000); + await page.waitForTimeout(2000); // After clicking, it will redirect to another page, so we need to get the instance of the new page const newPage = page.context().pages()[1]; diff --git a/packages/core/client/src/modules/popup/__e2e__/templatesOfBug.ts b/packages/core/client/src/modules/popup/__e2e__/templatesOfBug.ts index 2d71859186..680585837d 100644 --- a/packages/core/client/src/modules/popup/__e2e__/templatesOfBug.ts +++ b/packages/core/client/src/modules/popup/__e2e__/templatesOfBug.ts @@ -5124,3 +5124,236 @@ export const zIndexOfSubpage = { 'x-index': 1, }, }; +export const zIndexEditProfile = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-app-version': '1.3.32-beta', + properties: { + '17v3l3wra1q': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-app-version': '1.3.32-beta', + properties: { + tpentznu41j: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.32-beta', + properties: { + '16wj734kw8c': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.32-beta', + properties: { + gh2evps4nft: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.32-beta', + 'x-uid': 'jlclfunyxlo', + 'x-async': false, + 'x-index': 1, + }, + juqs9wupfcc: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.32-beta', + properties: { + onge8etkwkq: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.32-beta', + properties: { + '44abnpgy9hl': { + 'x-uid': '2m4j1wrmt2k', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'open subpage', + 'x-action': 'view', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:view', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'page', + iconColor: '#1677FF', + danger: false, + }, + 'x-action-context': { + dataSource: 'main', + collection: 'users', + }, + 'x-decorator': 'ACLActionProvider', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + 'x-uid': 'pncs8uoz02h', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'i1n4wf3wqt8', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '537y7twqq1x', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'j652bs2ocx7', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'lmmfco4ti1k', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'qxsdgdxd7zd', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'rxecd82tr2t', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '8mdf3toqg65', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'x2e6vc3l16a', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'eqlzylp6c53', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '2e8trd4olie', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ay7xv8zc868', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/core/client/src/modules/popup/__e2e__/zIndex.test.ts b/packages/core/client/src/modules/popup/__e2e__/zIndex.test.ts index 3d05489c24..a77e96d27f 100644 --- a/packages/core/client/src/modules/popup/__e2e__/zIndex.test.ts +++ b/packages/core/client/src/modules/popup/__e2e__/zIndex.test.ts @@ -8,7 +8,7 @@ */ import { expect, test } from '@nocobase/test/e2e'; -import { T2797, T2838, zIndexOfSubpage } from './templatesOfBug'; +import { T2797, T2838, zIndexEditProfile, zIndexOfSubpage } from './templatesOfBug'; test.describe('z-index of dialog', () => { test('edit block title', async ({ page, mockPage }) => { @@ -73,4 +73,32 @@ test.describe('z-index of dialog', () => { .click(); await expect(page.getByText('Add condition', { exact: true })).toBeVisible(); }); + + test('edit profile', async ({ page, mockPage }) => { + await mockPage(zIndexEditProfile).goto(); + + // open subpage, and then open the Edit Profile drawer + await page.getByLabel('action-Action.Link-open').click(); + await page.getByTestId('user-center-button').hover(); + await page.getByRole('menuitem', { name: 'Edit profile' }).click(); + await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).toBeVisible(); + + // click the Cancel button to close the drawer + await page.getByLabel('action-Action-Cancel').click(); + await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).not.toBeVisible(); + }); + + test('change password', async ({ page, mockPage }) => { + await mockPage(zIndexEditProfile).goto(); + + // open subpage, and then open the Change password drawer + await page.getByLabel('action-Action.Link-open').click(); + await page.getByTestId('user-center-button').hover(); + await page.getByRole('menuitem', { name: 'Change password' }).click(); + await expect(page.getByTestId('drawer-Action.Drawer-Change password')).toBeVisible(); + + // click the Cancel button to close the drawer + await page.getByLabel('action-Action-Cancel').click(); + await expect(page.getByTestId('drawer-Action.Drawer-Change password')).not.toBeVisible(); + }); }); diff --git a/packages/core/client/src/modules/variable/__e2e__/parentObject.test.ts b/packages/core/client/src/modules/variable/__e2e__/parentObject.test.ts new file mode 100644 index 0000000000..f71ce93942 --- /dev/null +++ b/packages/core/client/src/modules/variable/__e2e__/parentObject.test.ts @@ -0,0 +1,135 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { expect, test } from '@nocobase/test/e2e'; +import { inDefaultValue } from './templates'; + +test.describe('variable: parent object', () => { + test('in default value', async ({ page, mockPage }) => { + await mockPage(inDefaultValue).goto(); + + // 1. 在当前表单中的子表单中,使用 “当前表单” 变量为 text2 字段设置默认值 + await page.getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2').hover(); + await page + .getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection2-collection2.text2', { + exact: true, + }) + .hover(); + await page.getByRole('menuitem', { name: 'Set default value' }).click(); + await page.getByLabel('variable-button').click(); + await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click(); + await page.getByRole('menuitemcheckbox', { name: 'text1' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + + // 2. 在子表单中的子表格中,使用 “上级对象” 变量为 text3 字段设置默认值 + await page.getByRole('button', { name: 'text3' }).click(); + await page.getByLabel('designer-schema-settings-TableV2.Column-fieldSettings:TableColumn-collection3').click(); + await page.getByRole('menuitem', { name: 'Set default value' }).click(); + await page.getByLabel('variable-button').click(); + await page.getByRole('menuitemcheckbox', { name: 'Parent object right' }).click(); + await page.getByRole('menuitemcheckbox', { name: 'text2' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + + // 3. 当更改当前表单中的 text1 字段后,text2 和 text3 字段应该也会被自动更改 + await page.getByRole('button', { name: 'Add new' }).click(); + await page + .getByLabel('block-item-CollectionField-collection1-form-collection1.text1-text1') + .getByRole('textbox') + .fill('123456abcdefg'); + await expect( + page.getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2').getByRole('textbox'), + ).toHaveValue('123456abcdefg'); + await expect( + page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').getByRole('textbox'), + ).toHaveValue('123456abcdefg'); + }); + + test('in linkage rules', async ({ page, mockPage }) => { + await mockPage(inDefaultValue).goto(); + + // 1. Use "Current form" and "Parent object" variables in nested subforms and subtables + await page.getByLabel('block-item-CollectionField-collection1-form-collection1.m2m1-m2m1').hover(); + await page + .getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection1-collection1.m2m1', { + exact: true, + }) + .hover(); + await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('button', { name: 'plus Add linkage rule' }).click(); + await page.getByText('Add property').click(); + await page.getByTestId('select-linkage-property-field').click(); + await page.getByTitle('text2').click(); + await page.getByTestId('select-linkage-action-field').click(); + await page.getByRole('option', { name: 'Value', exact: true }).click(); + await page.getByTestId('select-linkage-value-type').click(); + await page.getByTitle('Expression').click(); + await page.getByLabel('variable-button').click(); + await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click(); + await page.getByRole('menuitemcheckbox', { name: 'text1' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + + await page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').hover(); + await page + .getByLabel('designer-schema-settings-CollectionField-fieldSettings:FormItem-collection2-collection2.m2m2', { + exact: true, + }) + .hover(); + await page.getByRole('menuitem', { name: 'Linkage rules' }).click(); + await page.getByRole('button', { name: 'plus Add linkage rule' }).click(); + await page.getByText('Add property').click(); + await page.getByTestId('select-linkage-property-field').click(); + await page.getByTitle('text3').click(); + await page.getByTestId('select-linkage-action-field').click(); + await page.getByRole('option', { name: 'Value', exact: true }).click(); + await page.getByTestId('select-linkage-value-type').click(); + await page.getByTitle('Expression').click(); + await page.getByLabel('variable-button').click(); + await page.getByRole('menuitemcheckbox', { name: 'Parent object right' }).click(); + await page.getByRole('menuitemcheckbox', { name: 'text2' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + + // 2. Assert: When the text1 field in the current form is changed, the text2 and text3 fields should also be automatically changed + await page.getByRole('button', { name: 'Add new' }).click(); + await page + .getByLabel('block-item-CollectionField-collection1-form-collection1.text1-text1') + .getByRole('textbox') + .fill('123456abcdefg'); + + await expect( + page.getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2').getByRole('textbox'), + ).toHaveValue('123456abcdefg'); + await expect( + page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').getByRole('textbox'), + ).toHaveValue('123456abcdefg'); + + // 3. Test if the "Current object" variable can be used normally in the subform + await page.getByLabel('schema-initializer-Grid-form:configureFields-collection2').hover(); + await page.getByRole('menuitem', { name: 'form Add Markdown' }).click(); + await page.getByLabel('block-item-Markdown.Void-').hover(); + await page.getByLabel('designer-schema-settings-Markdown.Void-blockSettings:markdown-collection2').hover(); + await page.getByRole('menuitem', { name: 'Edit markdown' }).click(); + await page.getByText('This is a demo text, **').click(); + await page.getByText('This is a demo text, **').clear(); + await page.getByLabel('variable-button').click(); + await page.getByRole('menuitemcheckbox', { name: 'Current object right' }).click(); + await page.getByRole('menuitemcheckbox', { name: 'text2' }).click(); + await page.getByRole('button', { name: 'Save' }).click(); + + await page + .getByLabel('block-item-CollectionField-collection2-form-collection2.text2-text2') + .getByRole('textbox') + .fill('987654321'); + + // 4. Assert: The subtable and Markdown should be updated in real-time + await expect( + page.getByLabel('block-item-CollectionField-collection2-form-collection2.m2m2-m2m2').getByRole('textbox'), + ).toHaveValue('987654321'); + // await expect(page.getByLabel('block-item-Markdown.Void-')).toHaveText('987654321'); + }); +}); diff --git a/packages/core/client/src/modules/variable/__e2e__/templates.ts b/packages/core/client/src/modules/variable/__e2e__/templates.ts index c502a0955a..f5c59fee6d 100644 --- a/packages/core/client/src/modules/variable/__e2e__/templates.ts +++ b/packages/core/client/src/modules/variable/__e2e__/templates.ts @@ -1297,3 +1297,406 @@ export const tableSelectedRecords = { 'x-index': 1, }, }; +export const inDefaultValue = { + collections: [ + { + name: 'collection1', + fields: [ + { + name: 'text1', + interface: 'input', + }, + { + name: 'm2m1', + interface: 'm2m', + target: 'collection2', + }, + ], + }, + { + name: 'collection2', + fields: [ + { + name: 'text2', + interface: 'input', + }, + { + name: 'm2m2', + interface: 'm2m', + target: 'collection3', + }, + ], + }, + { + name: 'collection3', + fields: [ + { + name: 'text3', + interface: 'input', + }, + ], + }, + ], + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + qiis77b2b96: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + properties: { + xn73tu52o4l: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + ovrxf0qi4oh: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + clq66owv5vt: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'collection1:create', + 'x-decorator': 'FormBlockProvider', + 'x-use-decorator-props': 'useCreateFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'collection1', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + 'x-app-version': '1.3.33-beta', + properties: { + jgyr5k5rhl5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useCreateFormBlockProps', + 'x-app-version': '1.3.33-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + 'x-app-version': '1.3.33-beta', + properties: { + '704zd4gqwia': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + bng2scwwp21: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + text1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'collection1.text1', + 'x-component-props': {}, + 'x-app-version': '1.3.33-beta', + 'x-uid': '8yrtgs4fij4', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5mpw6xv5t53', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'umhyk321or1', + 'x-async': false, + 'x-index': 1, + }, + '7jmmz0am2mp': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + kuwqh6jsb0z: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + m2m1: { + 'x-uid': '4cojuep3jug', + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'collection1.m2m1', + 'x-component-props': { + fieldNames: { + label: 'id', + value: 'id', + }, + mode: 'Nester', + }, + 'x-app-version': '1.3.33-beta', + default: null, + properties: { + sqommd77rxp: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'AssociationField.Nester', + 'x-index': 1, + 'x-app-version': '1.3.33-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'form:configureFields', + 'x-app-version': '1.3.33-beta', + properties: { + '7pnogkc6aso': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + xsbs3warqhf: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + text2: { + 'x-uid': 's0lsw2l9gxo', + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'collection2.text2', + 'x-component-props': {}, + 'x-app-version': '1.3.33-beta', + default: null, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'fh40b1ec8xe', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'twc2iso6ij8', + 'x-async': false, + 'x-index': 1, + }, + '758esk132v5': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + t9ijtjbpryx: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + m2m2: { + 'x-uid': 'dgul9qn182o', + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'collection2.m2m2', + 'x-component-props': { + fieldNames: { + label: 'id', + value: 'id', + }, + mode: 'SubTable', + }, + 'x-app-version': '1.3.33-beta', + default: null, + properties: { + wzyleesvy5a: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'AssociationField.SubTable', + 'x-initializer': 'table:configureColumns', + 'x-initializer-props': { + action: false, + }, + 'x-index': 1, + 'x-app-version': '1.3.33-beta', + properties: { + '1rhnqhhrxtl': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.3.33-beta', + properties: { + text3: { + 'x-uid': 'qr2z1604tdt', + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'collection3.text3', + 'x-component': 'CollectionField', + 'x-component-props': { + ellipsis: true, + }, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.3.33-beta', + default: null, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'veibwzrxmwt', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '12y4qwbwh9v', + 'x-async': false, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'fjqt5n7vnp1', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'p5xwk5asiyx', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'rv2r7oq9i5v', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'mk2emm6340d', + 'x-async': false, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'iibkaselueb', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'cgkroe30mh2', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'ccpvbj4s1fn', + 'x-async': false, + 'x-index': 1, + }, + u9ryrklw5oj: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'createForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': '0ib597ro9p7', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'gx13vgubf5i', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '01nwdjwsedu', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'cxse6wcqnm3', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'pp209qnmn8v', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'i11l6wkz6b2', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'a9u1cjps5th', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx b/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx index de83465b04..cbbac0c10f 100644 --- a/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx +++ b/packages/core/client/src/plugin-manager/PinnedPluginListProvider.tsx @@ -27,7 +27,7 @@ export const PinnedPluginListProvider: React.FC<{ items: any }> = (props) => { export const PinnedPluginList = () => { const { allowAll, snippets } = useACLRoleContext(); const getSnippetsAllow = (aclKey) => { - return allowAll || snippets?.includes(aclKey); + return allowAll || aclKey === '*' || snippets?.includes(aclKey); }; const ctx = useContext(PinnedPluginListContext); const { components } = useContext(SchemaOptionsContext); diff --git a/packages/core/client/src/pm/PluginForm/modal/PluginAddModal.tsx b/packages/core/client/src/pm/PluginForm/modal/PluginAddModal.tsx index c78190b725..7e2a7c1f05 100644 --- a/packages/core/client/src/pm/PluginForm/modal/PluginAddModal.tsx +++ b/packages/core/client/src/pm/PluginForm/modal/PluginAddModal.tsx @@ -27,7 +27,7 @@ export const PluginAddModal: FC = ({ onClose, isShow }) => { const [type, setType] = useState<'npm' | 'upload' | 'url'>('npm'); return ( - onClose()} footer={null} destroyOnClose title={t('Add & update')} width={580} open={isShow}> + onClose()} footer={null} destroyOnClose title={t('Add & Update')} width={580} open={isShow}> {/* */}
setType(e.target.value)}> diff --git a/packages/core/client/src/record-provider/index.tsx b/packages/core/client/src/record-provider/index.tsx index 9460accb60..39cc9805e1 100644 --- a/packages/core/client/src/record-provider/index.tsx +++ b/packages/core/client/src/record-provider/index.tsx @@ -7,6 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { raw } from '@formily/reactive'; import React, { createContext, useContext, useMemo } from 'react'; import { CollectionRecordProvider, useCollection } from '../data-source'; import { useCurrentUserContext } from '../user'; @@ -28,7 +29,8 @@ export const RecordProvider: React.FC<{ const { record, children, parent, isNew } = props; const collection = useCollection(); const value = useMemo(() => { - const res = { ...record }; + // Directly destructuring reactive objects can cause performance issues, so we use raw to wrap it here + const res = { ...raw(record) }; res['__parent'] = parent; res['__collectionName'] = collection?.name; return res; diff --git a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx index 0d5446fbec..a92be9ecfc 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx @@ -500,20 +500,17 @@ export function WorkflowConfig() { const buttonAction = fieldSchema['x-action']; const description = { - submit: t( - 'Workflow will be triggered before or after submitting succeeded based on workflow type (supports pre/post action event in local mode, and approval event).', - { - ns: 'workflow', - }, - ), + submit: t('Support pre-action event (local mode), post-action event (local mode), and approval event here.', { + ns: 'workflow', + }), 'customize:save': t( - 'Workflow will be triggered before or after submitting succeeded based on workflow type (supports pre/post action event in local mode, and approval event).', + 'Support pre-action event (local mode), post-action event (local mode), and approval event here.', { ns: 'workflow', }, ), 'customize:update': t( - 'Workflow will be triggered before or after submitting succeeded based on workflow type (supports pre/post action event in local mode, and approval event).', + 'Support pre-action event (local mode), post-action event (local mode), and approval event here.', { ns: 'workflow' }, ), 'customize:triggerWorkflows': t( diff --git a/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx b/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx index 4a1c7da78f..9847e5c9c2 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx @@ -37,7 +37,7 @@ const openSizeWidthMap = new Map([ ]); export const InternalActionDrawer: React.FC = observer( (props) => { - const { footerNodeName = 'Action.Drawer.Footer', ...others } = props; + const { footerNodeName = 'Action.Drawer.Footer', zIndex: _zIndex, ...others } = props; const { visible, setVisible, openSize = 'middle', drawerProps, modalProps } = useActionContext(); const schema = useFieldSchema(); const field = useField(); @@ -63,7 +63,7 @@ export const InternalActionDrawer: React.FC = observer( useSetAriaLabelForDrawer(visible); } - const zIndex = parentZIndex + (props.level || 0); + const zIndex = _zIndex || parentZIndex + (props.level || 0); return ( diff --git a/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx b/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx index bf054edf25..c22ae81581 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Modal.tsx @@ -8,7 +8,7 @@ */ import { css } from '@emotion/css'; -import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react'; +import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; import { Modal, ModalProps } from 'antd'; import classNames from 'classnames'; import React, { useMemo } from 'react'; @@ -39,11 +39,10 @@ const openSizeWidthMap = new Map([ export const InternalActionModal: React.FC> = observer( (props) => { - const { footerNodeName = 'Action.Modal.Footer', width, ...others } = props; + const { footerNodeName = 'Action.Modal.Footer', width, zIndex: _zIndex, ...others } = props; const { visible, setVisible, openSize = 'middle', modalProps } = useActionContext(); const actualWidth = width ?? openSizeWidthMap.get(openSize); const schema = useFieldSchema(); - const form = useForm(); const field = useField(); const { token } = useToken(); const tabContext = useTabsContext(); @@ -71,7 +70,7 @@ export const InternalActionModal: React.FC> = obse useSetAriaLabelForModal(visible); } - const zIndex = parentZIndex + (props.level || 0); + const zIndex = _zIndex || parentZIndex + (props.level || 0); return ( @@ -91,7 +90,6 @@ export const InternalActionModal: React.FC> = obse open={visible} onCancel={() => { setVisible(false, true); - form.reset(); }} className={classNames( others.className, diff --git a/packages/core/client/src/schema-component/antd/action/Action.style.ts b/packages/core/client/src/schema-component/antd/action/Action.style.ts index 1f4d4061b7..10ee19a305 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.style.ts +++ b/packages/core/client/src/schema-component/antd/action/Action.style.ts @@ -47,6 +47,13 @@ const useStyles = genStyleHook('nb-action', (token) => { }, }, }, + + '.ant-btn-icon': { + marginInlineEnd: '0px !important', + }, + '.nb-action-title': { + marginInlineStart: `${token.controlPaddingHorizontalSM}px`, + }, }, }; }); diff --git a/packages/core/client/src/schema-component/antd/action/Action.tsx b/packages/core/client/src/schema-component/antd/action/Action.tsx index 3174959b97..368232f924 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.tsx @@ -7,15 +7,16 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react'; +import { Field } from '@formily/core'; +import { observer, RecursionField, Schema, useField, useFieldSchema, useForm } from '@formily/react'; import { isPortalInBody } from '@nocobase/utils/client'; import { App, Button } from 'antd'; import classnames from 'classnames'; -import _, { default as lodash } from 'lodash'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { default as lodash } from 'lodash'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useTranslation } from 'react-i18next'; -import { ErrorFallback, StablePopover, useActionContext } from '../..'; +import { ErrorFallback, StablePopover, TabsContextProvider, useActionContext } from '../..'; import { useDesignable } from '../../'; import { useACLActionParamsContext } from '../../../acl'; import { useCollectionParentRecordData, useCollectionRecordData, useDataBlockRequest } from '../../../data-source'; @@ -40,7 +41,7 @@ import { ActionPage } from './Action.Page'; import useStyles from './Action.style'; import { ActionContextProvider } from './context'; import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction'; -import { ActionProps, ComposedAction } from './types'; +import { ActionContextProps, ActionProps, ComposedAction } from './types'; import { linkageAction, setInitialActionState } from './utils'; const useA = () => { @@ -75,35 +76,22 @@ export const Action: ComposedAction = withDynamicSchemaProps( confirmTitle, ...others } = useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema - const aclCtx = useACLActionParamsContext(); - const { wrapSSR, componentCls, hashId } = useStyles(); const { t } = useTranslation(); - const { visibleWithURL, setVisibleWithURL } = usePopupUtils(); - const [visible, setVisible] = useState(false); - const [formValueChanged, setFormValueChanged] = useState(false); - const { setSubmitted: setParentSubmitted } = useActionContext(); const Designer = useDesigner(); const field = useField(); - const { run, element, disabled: disableAction } = _.isFunction(useAction) ? useAction(actionCallback) : ({} as any); const fieldSchema = useFieldSchema(); const compile = useCompile(); - const form = useForm(); const recordData = useCollectionRecordData(); - const parentRecordData = useCollectionParentRecordData(); - const designerProps = fieldSchema['x-toolbar-props'] || fieldSchema['x-designer-props']; - const openMode = fieldSchema?.['x-component-props']?.['openMode']; - const openSize = fieldSchema?.['x-component-props']?.['openSize']; - const refreshDataBlockRequest = fieldSchema?.['x-component-props']?.['refreshDataBlockRequest']; const confirm = compile(fieldSchema['x-component-props']?.confirm) || propsConfirm; - const disabled = form.disabled || field.disabled || field.data?.disabled || propsDisabled || disableAction; const linkageRules = useMemo(() => fieldSchema?.['x-linkage-rules'] || [], [fieldSchema?.['x-linkage-rules']]); const { designable } = useDesignable(); const tarComponent = useComponent(component) || component; - const { modal } = App.useApp(); const variables = useVariables(); const localVariables = useLocalVariables({ currentForm: { values: recordData, readPretty: false } as any }); + const { visibleWithURL, setVisibleWithURL } = usePopupUtils(); + const { setSubmitted } = useActionContext(); const { getAriaLabel } = useGetAriaLabelOfAction(title); - const service = useDataBlockRequest(); + const parentRecordData = useCollectionParentRecordData(); const actionTitle = useMemo(() => { const res = title || compile(fieldSchema.title); @@ -130,14 +118,6 @@ export const Action: ComposedAction = withDynamicSchemaProps( }); }, [field, linkageRules, localVariables, variables]); - const buttonStyle = useMemo(() => { - return { - ...style, - opacity: designable && (field?.data?.hidden || !aclCtx) && 0.1, - color: disabled ? 'rgba(0, 0, 0, 0.25)' : style?.color, - }; - }, [aclCtx, designable, field?.data?.hidden, style, disabled]); - const handleMouseEnter = useCallback( (e) => { onMouseEnter?.(e); @@ -145,124 +125,248 @@ export const Action: ComposedAction = withDynamicSchemaProps( [onMouseEnter], ); - const buttonProps = { - designable, - field, - aclCtx, - actionTitle, - icon, - loading, - disabled, - buttonStyle, - handleMouseEnter, - tarComponent, - designerProps, - componentCls, - hashId, - className, - others, - getAriaLabel, - type: props.type, - Designer, - openMode, - onClick, - refreshDataBlockRequest, - service, - fieldSchema, - setVisible, - run, - confirm, - modal, - setSubmitted: setParentSubmitted, - confirmTitle, - }; - - const buttonElement = RenderButton(buttonProps); - // if (!btnHover) { - // return buttonElement; - // } - - const result = ( - - { - setVisible?.(value); - setVisibleWithURL?.(value); - }} - formValueChanged={formValueChanged} - setFormValueChanged={setFormValueChanged} - openMode={openMode} - openSize={openSize} - containerRefKey={containerRefKey} - fieldSchema={fieldSchema} - setSubmitted={setParentSubmitted} - > - {popover && } - {!popover && } - {!popover && props.children} - {element} - - + return ( + ); - - // fix https://nocobase.height.app/T-3235/description - if (addChild) { - return wrapSSR( - // fix https://nocobase.height.app/T-3966 - - {result} - , - ); - } - - return wrapSSR(result); }), { displayName: 'Action' }, ); -Action.Popover = observer( - (props) => { - const { button, visible, setVisible } = useActionContext(); - const content = ( - - {props.children} - - ); - return ( - { - setVisible(visible); - }} - content={content} - > - {button} - - ); - }, - { displayName: 'Action.Popover' }, -); +interface InternalActionProps { + containerRefKey: ActionContextProps['containerRefKey']; + fieldSchema: Schema; + designable: boolean; + field: Field; + actionTitle: string; + icon: string; + loading: boolean; + handleMouseEnter: (e: React.MouseEvent) => void; + tarComponent: React.ElementType; + className: string; + type: string; + Designer: React.ElementType; + onClick: (e: React.MouseEvent) => void; + confirm: { + enable: boolean; + content: string; + title: string; + }; + confirmTitle: string; + popover: boolean; + addChild: boolean; + recordData: any; + title: string; + style: React.CSSProperties; + propsDisabled: boolean; + useAction: (actionCallback: (...args: any[]) => any) => { + run: () => void; + element: React.ReactNode; + disabled: boolean; + }; + actionCallback: (...args: any[]) => any; + visibleWithURL: boolean; + setVisibleWithURL: (visible: boolean) => void; + setSubmitted: (v: boolean) => void; + getAriaLabel: (postfix?: string) => string; + parentRecordData: any; +} -Action.Popover.Footer = observer( - (props) => { - return ( -
= observer(function Com(props) { + const { + containerRefKey, + fieldSchema, + designable, + field, + actionTitle, + icon, + loading, + handleMouseEnter, + tarComponent, + className, + type, + Designer, + onClick, + confirm, + confirmTitle, + popover, + addChild, + recordData, + title, + style, + propsDisabled, + useAction, + actionCallback, + visibleWithURL, + setVisibleWithURL, + setSubmitted, + getAriaLabel, + parentRecordData, + ...others + } = props; + const [visible, setVisible] = useState(false); + const { wrapSSR, componentCls, hashId } = useStyles(); + const [formValueChanged, setFormValueChanged] = useState(false); + const designerProps = fieldSchema['x-toolbar-props'] || fieldSchema['x-designer-props']; + const openMode = fieldSchema?.['x-component-props']?.['openMode']; + const openSize = fieldSchema?.['x-component-props']?.['openSize']; + const refreshDataBlockRequest = fieldSchema?.['x-component-props']?.['refreshDataBlockRequest']; + const { modal } = App.useApp(); + const form = useForm(); + const aclCtx = useACLActionParamsContext(); + const { run, element, disabled: disableAction } = useAction?.(actionCallback) || ({} as any); + const disabled = form.disabled || field.disabled || field.data?.disabled || propsDisabled || disableAction; + + const buttonStyle = useMemo(() => { + return { + ...style, + opacity: designable && (field?.data?.hidden || !aclCtx) && 0.1, + color: disabled ? 'rgba(0, 0, 0, 0.25)' : style?.color, + }; + }, [aclCtx, designable, field?.data?.hidden, style, disabled]); + + const buttonProps = { + designable, + field, + aclCtx, + actionTitle, + icon, + loading, + disabled, + buttonStyle, + handleMouseEnter, + tarComponent, + designerProps, + componentCls, + hashId, + className, + others, + getAriaLabel, + type, + Designer, + openMode, + onClick, + refreshDataBlockRequest, + fieldSchema, + setVisible, + run, + confirm, + modal, + setSubmitted, + confirmTitle, + }; + + let result = ( + + { + setVisible?.(value); + setVisibleWithURL?.(value); }} + formValueChanged={formValueChanged} + setFormValueChanged={setFormValueChanged} + openMode={openMode} + openSize={openSize} + containerRefKey={containerRefKey} + fieldSchema={fieldSchema} + setSubmitted={setSubmitted} > - {props.children} -
- ); - }, - { displayName: 'Action.Popover.Footer' }, -); + {popover && } + {!popover && } + {!popover && props.children} + {element} + + + ); + + if (isBulkEditAction(fieldSchema)) { + // Clear the context of Tabs to avoid affecting the Tabs of the upper-level popup + result = {result}; + } + + if (addChild) { + return wrapSSR( + + {result} + , + ) as React.ReactElement; + } + + return wrapSSR(result) as React.ReactElement; +}); + +InternalAction.displayName = 'InternalAction'; + +Action.Popover = function ActionPopover(props) { + const { button, visible, setVisible } = useActionContext(); + const content = ( + + {props.children} + + ); + return ( + { + setVisible(visible); + }} + content={content} + > + {button} + + ); +}; + +Action.Popover.displayName = 'Action.Popover'; + +Action.Popover.Footer = (props) => { + return ( +
+ {props.children} +
+ ); +}; + +Action.Popover.Footer.displayName = 'Action.Popover.Footer'; Action.Link = ActionLink; Action.Designer = ActionDesigner; @@ -300,7 +404,6 @@ function RenderButton({ openMode, onClick, refreshDataBlockRequest, - service, fieldSchema, setVisible, run, @@ -309,9 +412,17 @@ function RenderButton({ setSubmitted, confirmTitle, }) { + const service = useDataBlockRequest(); const { t } = useTranslation(); const { isPopupVisibleControlledByURL } = usePopupSettings(); const { openPopup } = usePopupUtils(); + + const serviceRef = useRef(null); + serviceRef.current = service; + + const openPopupRef = useRef(null); + openPopupRef.current = openPopup; + const handleButtonClick = useCallback( (e: React.MouseEvent, checkPortal = true) => { if (checkPortal && isPortalInBody(e.target as Element)) { @@ -326,7 +437,7 @@ function RenderButton({ onClick(e, () => { if (refreshDataBlockRequest !== false) { setSubmitted?.(true); - service?.refresh?.(); + serviceRef.current?.refresh?.(); } }); } else if (isBulkEditAction(fieldSchema) || !isPopupVisibleControlledByURL()) { @@ -338,7 +449,7 @@ function RenderButton({ ['view', 'update', 'create', 'customize:popup'].includes(fieldSchema['x-action']) && fieldSchema['x-uid'] ) { - openPopup(); + openPopupRef.current(); } else { setVisible(true); run?.(); @@ -357,44 +468,118 @@ function RenderButton({ } }, [ + disabled, aclCtx, - actionTitle, + confirm?.enable, confirm?.content, confirm?.title, - confirm?.enable, - disabled, - modal, onClick, - openPopup, + fieldSchema, + isPopupVisibleControlledByURL, refreshDataBlockRequest, - run, - service, + setSubmitted, setVisible, + run, + modal, t, + confirmTitle, + actionTitle, ], ); - if (!designable && (field?.data?.hidden || !aclCtx)) { - return null; - } - return ( - : icon} + - {actionTitle} - - + buttonStyle={buttonStyle} + handleMouseEnter={handleMouseEnter} + getAriaLabel={getAriaLabel} + handleButtonClick={handleButtonClick} + tarComponent={tarComponent} + componentCls={componentCls} + hashId={hashId} + className={className} + type={type} + Designer={Designer} + designerProps={designerProps} + {...others} + /> ); } + +const RenderButtonInner = observer( + (props: { + designable: boolean; + field: Field; + aclCtx: any; + actionTitle: string; + icon: string; + loading: boolean; + disabled: boolean; + buttonStyle: React.CSSProperties; + handleMouseEnter: (e: React.MouseEvent) => void; + getAriaLabel: (postfix?: string) => string; + handleButtonClick: (e: React.MouseEvent) => void; + tarComponent: React.ElementType; + componentCls: string; + hashId: string; + className: string; + type: string; + Designer: React.ElementType; + designerProps: any; + }) => { + const { + designable, + field, + aclCtx, + actionTitle, + icon, + loading, + disabled, + buttonStyle, + handleMouseEnter, + getAriaLabel, + handleButtonClick, + tarComponent, + componentCls, + hashId, + className, + type, + Designer, + designerProps, + ...others + } = props; + + if (!designable && (field?.data?.hidden || !aclCtx)) { + return null; + } + + return ( + : icon} + disabled={disabled} + style={buttonStyle} + onClick={handleButtonClick} + component={tarComponent || Button} + className={classnames(componentCls, hashId, className, 'nb-action')} + type={type === 'danger' ? undefined : type} + > + {actionTitle && {actionTitle}} + + + ); + }, +); + +RenderButtonInner.displayName = 'RenderButtonInner'; diff --git a/packages/core/client/src/schema-component/antd/action/__tests__/action.test.tsx b/packages/core/client/src/schema-component/antd/action/__tests__/action.test.tsx index 9fb0d4f6e5..4bf9848586 100644 --- a/packages/core/client/src/schema-component/antd/action/__tests__/action.test.tsx +++ b/packages/core/client/src/schema-component/antd/action/__tests__/action.test.tsx @@ -106,7 +106,7 @@ describe('Action.Popover', () => { const { container } = render(); const btn = container.querySelector('.ant-btn') as HTMLElement; - fireEvent.mouseEnter(btn); + fireEvent.click(btn); await waitFor(() => { // popover diff --git a/packages/core/client/src/schema-component/antd/action/context.tsx b/packages/core/client/src/schema-component/antd/action/context.tsx index 14617b3b49..8eb71bd256 100644 --- a/packages/core/client/src/schema-component/antd/action/context.tsx +++ b/packages/core/client/src/schema-component/antd/action/context.tsx @@ -45,7 +45,7 @@ const useIsSubPageClosedByPageMenu = () => { export const ActionContextProvider: React.FC = (props) => { const [submitted, setSubmitted] = useState(false); //是否有提交记录 - const { visible } = { ...props, ...props.value } || {}; + const { visible } = { ...props, ...props.value }; const { setSubmitted: setParentSubmitted } = { ...props, ...props.value }; const service = useBlockServiceInActionButton(); const isSubPageClosedByPageMenu = useIsSubPageClosedByPageMenu(); diff --git a/packages/core/client/src/schema-component/antd/action/demos/demo4.tsx b/packages/core/client/src/schema-component/antd/action/demos/demo4.tsx index 75d797c7b9..17dfc7355d 100644 --- a/packages/core/client/src/schema-component/antd/action/demos/demo4.tsx +++ b/packages/core/client/src/schema-component/antd/action/demos/demo4.tsx @@ -1,4 +1,4 @@ -import { ISchema, observer, useForm } from '@formily/react'; +import { ISchema, useForm } from '@formily/react'; import { Action, CustomRouterContextProvider, @@ -53,7 +53,7 @@ const schema: ISchema = { }, }; -export default observer(() => { +export default () => { return ( @@ -63,4 +63,4 @@ export default observer(() => { ); -}); +}; diff --git a/packages/core/client/src/schema-component/antd/action/index.tsx b/packages/core/client/src/schema-component/antd/action/index.tsx index 22db0e3b38..80766dbb69 100644 --- a/packages/core/client/src/schema-component/antd/action/index.tsx +++ b/packages/core/client/src/schema-component/antd/action/index.tsx @@ -8,6 +8,7 @@ */ export * from './Action'; +export * from './Action.Designer'; export * from './ActionBar'; export * from './context'; export * from './hooks'; @@ -15,5 +16,5 @@ export * from './hooks/useGetAriaLabelOfAction'; export * from './hooks/useGetAriaLabelOfDrawer'; export * from './hooks/useGetAriaLabelOfModal'; export * from './hooks/useGetAriaLabelOfPopover'; -export * from './Action.Designer'; export * from './types'; +export * from './zIndexContext'; diff --git a/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx b/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx index 42e7e2daf0..e62aea2aac 100644 --- a/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx +++ b/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx @@ -12,13 +12,7 @@ import { Tag, TreeSelect } from 'antd'; import type { DefaultOptionType, TreeSelectProps } from 'rc-tree-select/es/TreeSelect'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { - CollectionFieldOptions_deprecated, - parseCollectionName, - useApp, - useCollectionManager_deprecated, - useCompile, -} from '../../..'; +import { CollectionFieldOptions_deprecated, parseCollectionName, useApp, useCompile } from '../../..'; export type AppendsTreeSelectProps = { value: string[] | string; @@ -106,7 +100,11 @@ export const AppendsTreeSelect: React.FC { + const instance = collectionManager.getCollection(name); + // NOTE: condition for compatibility with hidden collections like "attachments" + return instance ? instance.getAllFields(predicate) : []; + }; const treeData = Object.values(optionsMap); const value: string | DefaultOptionType[] = useMemo(() => { if (props.multiple) { diff --git a/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx b/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx index 2f774ea1b2..d46174cbdb 100644 --- a/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/AssociationFieldProvider.tsx @@ -43,8 +43,6 @@ export const AssociationFieldProvider = observer( [fieldSchema['x-component-props']?.mode], ); - const fieldValue = useMemo(() => JSON.stringify(field.value), [field.value]); - const [loading, setLoading] = useState(!field.readPretty); useEffect(() => { @@ -93,8 +91,7 @@ export const AssociationFieldProvider = observer( field.value = []; } setLoading(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentMode, collectionField, fieldValue]); + }, [currentMode, collectionField, field]); if (loading) { return null; @@ -102,7 +99,7 @@ export const AssociationFieldProvider = observer( return collectionField ? ( {props.children} diff --git a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx index 4097ce0591..65611d78cb 100644 --- a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx @@ -13,6 +13,7 @@ import { RecursionField, connect, mapProps, observer, useField, useFieldSchema, import { uid } from '@formily/shared'; import { Space, message } from 'antd'; import { isFunction } from 'mathjs'; +import { last } from 'lodash'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ClearCollectionFieldContext, RecordProvider, useAPIClient, useCollectionRecordData } from '../../../'; @@ -82,7 +83,7 @@ const InternalAssociationSelect = observer( if ( linkageFields.includes(fieldPath?.props?.name) && field.value && - fieldPath?.indexes?.[0] === field?.indexes?.[0] && + last(fieldPath?.indexes) === last(field?.indexes) && fieldPath?.props?.name !== field.props.name ) { field.setValue(undefined); diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx index ea476f9300..c66d4d42fc 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalNester.tsx @@ -9,6 +9,7 @@ import { css, cx } from '@emotion/css'; import { FormLayout } from '@formily/antd-v5'; +import { theme } from 'antd'; import { RecursionField, observer, useField, useFieldSchema } from '@formily/react'; import React, { useEffect } from 'react'; import { ACLCollectionProvider, useACLActionParamsContext } from '../../../acl'; @@ -45,13 +46,25 @@ export const InternalNester = observer( const { options: collectionField } = useAssociationFieldContext(); const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true; const { actionName } = useACLActionParamsContext(); + const { token } = theme.useToken(); + const { + layout = 'vertical', + labelAlign = 'left', + labelWidth = 120, + labelWrap = true, + } = fieldSchema?.['x-component-props'] || {}; useEffect(() => { insertNester(schema.Nester); }, []); return ( - +
span:last-child { + display: none !important; + } + .ant-table-thead + button[aria-label*='schema-initializer-AssociationField.SubTable-table:configureColumns'] + > .ant-btn-icon { + margin: 0px; + } + .ant-table-tbody .nb-column-initializer { + min-width: 40px !important; + } `} layout={'vertical'} bordered={false} diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalTag.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalTag.tsx index 562c59a8ba..69be2cb74b 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalTag.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalTag.tsx @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { observer, useFieldSchema } from '@formily/react'; +import { useFieldSchema } from '@formily/react'; import { toArr } from '@formily/shared'; import React, { Fragment, useRef } from 'react'; import { useDesignable } from '../../'; @@ -100,9 +100,6 @@ const ButtonTabList: React.FC = (props) => { return <>{renderRecords()}; }; -export const ReadPrettyInternalTag: React.FC = observer( - (props: any) => { - return ; - }, - { displayName: 'ReadPrettyInternalTag' }, -); +export const ReadPrettyInternalTag: React.FC = (props: any) => { + return ; +}; diff --git a/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx b/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx index a1742a6aa2..0183ad36ad 100644 --- a/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/InternalViewer.tsx @@ -10,7 +10,7 @@ import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; import { toArr } from '@formily/shared'; import _ from 'lodash'; -import React, { FC, Fragment, useRef, useState } from 'react'; +import React, { FC, Fragment, useEffect, useRef, useState } from 'react'; import { useDesignable } from '../../'; import { WithoutTableFieldResource } from '../../../block-provider'; import { CollectionRecordProvider, useCollectionManager, useCollectionRecordData } from '../../../data-source'; @@ -49,6 +49,132 @@ export interface ButtonListProps { }; } +const RenderRecord = React.memo( + ({ + fieldNames, + isTreeCollection, + compile, + getLabelUiSchema, + collectionField, + snapshot, + enableLink, + designable, + insertViewer, + fieldSchema, + openPopup, + recordData, + ellipsisWithTooltipRef, + value, + setBtnHover, + }: { + fieldNames: any; + isTreeCollection: boolean; + compile: (source: any, ext?: any) => any; + getLabelUiSchema; + collectionField: any; + snapshot: boolean; + enableLink: any; + designable: boolean; + insertViewer: (ss: any) => void; + fieldSchema; + openPopup; + recordData: any; + ellipsisWithTooltipRef: React.MutableRefObject; + value: any; + setBtnHover: any; + }) => { + const [loading, setLoading] = useState(true); + const [result, setResult] = useState([]); + + // The map method here maybe quite time-consuming, especially in table blocks. + // Therefore, we use an asynchronous approach to render the list, + // which allows us to avoid blocking the main rendering process. + useEffect(() => { + const result = toArr(value).map((record, index, arr) => { + const value = record?.[fieldNames?.label || 'label']; + const label = isTreeCollection + ? transformNestedData(record) + .map((o) => o?.[fieldNames?.label || 'label']) + .join(' / ') + : isObject(value) + ? JSON.stringify(value) + : value; + + const val = toValue(compile(label), 'N/A'); + const labelUiSchema = getLabelUiSchema( + record?.__collection || collectionField?.target, + fieldNames?.label || 'label', + ); + const text = getLabelFormatValue(compile(labelUiSchema), val, true); + + return ( + + + {snapshot ? ( + text + ) : enableLink !== false ? ( + { + setBtnHover(true); + }} + onClick={(e) => { + setBtnHover(true); + e.stopPropagation(); + e.preventDefault(); + if (designable) { + insertViewer(schema.Viewer); + } + + if (fieldSchema.properties) { + openPopup({ + recordData: record, + parentRecordData: recordData, + }); + } + + ellipsisWithTooltipRef?.current?.setPopoverVisible(false); + }} + > + {text} + + ) : ( + text + )} + + {index < arr.length - 1 ? , : null} + + ); + }); + setResult(result); + setLoading(false); + }, [ + collectionField?.target, + compile, + designable, + ellipsisWithTooltipRef, + enableLink, + fieldNames?.label, + fieldSchema?.properties, + getLabelUiSchema, + insertViewer, + isTreeCollection, + openPopup, + recordData, + setBtnHover, + snapshot, + value, + ]); + + if (loading) { + return null; + } + + return <>{result}; + }, +); + +RenderRecord.displayName = 'RenderRecord'; + const ButtonLinkList: FC = (props) => { const fieldSchema = useFieldSchema(); const cm = useCollectionManager(); @@ -66,63 +192,25 @@ const ButtonLinkList: FC = (props) => { const { openPopup } = usePopupUtils(); const recordData = useCollectionRecordData(); - const renderRecords = () => - toArr(props.value).map((record, index, arr) => { - const value = record?.[fieldNames?.label || 'label']; - const label = isTreeCollection - ? transformNestedData(record) - .map((o) => o?.[fieldNames?.label || 'label']) - .join(' / ') - : isObject(value) - ? JSON.stringify(value) - : value; - const val = toValue(compile(label), 'N/A'); - const labelUiSchema = getLabelUiSchema( - record?.__collection || collectionField?.target, - fieldNames?.label || 'label', - ); - const text = getLabelFormatValue(compile(labelUiSchema), val, true); - return ( - - - {snapshot ? ( - text - ) : enableLink !== false ? ( - { - props.setBtnHover(true); - }} - onClick={(e) => { - props.setBtnHover(true); - e.stopPropagation(); - e.preventDefault(); - if (designable) { - insertViewer(schema.Viewer); - } - - // fix https://nocobase.height.app/T-4794/description - if (fieldSchema.properties) { - openPopup({ - recordData: record, - parentRecordData: recordData, - }); - } - - ellipsisWithTooltipRef?.current?.setPopoverVisible(false); - }} - > - {text} - - ) : ( - text - )} - - {index < arr.length - 1 ? , : null} - - ); - }); - - return <>{renderRecords()}; + return ( + + ); }; interface ReadPrettyInternalViewerProps { diff --git a/packages/core/client/src/schema-component/antd/association-field/ReadPretty.tsx b/packages/core/client/src/schema-component/antd/association-field/ReadPretty.tsx index bfba05a1e2..475085f1c5 100644 --- a/packages/core/client/src/schema-component/antd/association-field/ReadPretty.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/ReadPretty.tsx @@ -17,21 +17,18 @@ import { ReadPrettyInternalTag } from './InternalTag'; import { ReadPrettyInternalViewer } from './InternalViewer'; import { useAssociationFieldContext } from './hooks'; -const ReadPrettyAssociationField = observer( - (props: any) => { - const { currentMode } = useAssociationFieldContext(); - return ( - <> - {['Select', 'Picker', 'CascadeSelect'].includes(currentMode) && } - {currentMode === 'Tag' && } - {currentMode === 'Nester' && } - {currentMode === 'SubTable' && } - {currentMode === 'FileManager' && } - - ); - }, - { displayName: 'ReadPrettyAssociationField' }, -); +const ReadPrettyAssociationField = (props: any) => { + const { currentMode } = useAssociationFieldContext(); + return ( + <> + {['Select', 'Picker', 'CascadeSelect'].includes(currentMode) && } + {currentMode === 'Tag' && } + {currentMode === 'Nester' && } + {currentMode === 'SubTable' && } + {currentMode === 'FileManager' && } + + ); +}; export const ReadPretty = observer( (props) => { diff --git a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx index a700727e71..0abb554a1d 100644 --- a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx @@ -14,7 +14,7 @@ import { observer, RecursionField, useFieldSchema } from '@formily/react'; import { action } from '@formily/reactive'; import { isArr } from '@formily/shared'; import { Button } from 'antd'; -import React, { useContext, useMemo, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FormProvider, @@ -99,9 +99,8 @@ export const SubTable: any = observer( const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); const recordV2 = useCollectionRecord(); const collection = useCollection(); - + const { allowSelectExistingRecord, allowAddnew, allowDisassociation } = field.componentProps; useSubTableSpecialCase({ field }); - const move = (fromIndex: number, toIndex: number) => { if (toIndex === undefined) return; if (!isArr(field.value)) return; @@ -148,32 +147,58 @@ export const SubTable: any = observer( setSelectedRows, collectionField, }; + const usePickActionProps = () => { const { setVisible } = useActionContext(); const { selectedRows, setSelectedRows } = useContext(RecordPickerContext); return { onClick() { - selectedRows.map((v) => field.value.push(v)); + selectedRows.map((v) => field.value.push(markRecordAsNew(v))); field.onInput(field.value); field.initialValue = field.value; setSelectedRows([]); setVisible(false); + const totalPages = Math.ceil(field.value.length / (field.componentProps?.pageSize || 10)); + setCurrentPage(totalPages); }, }; }; const getFilter = () => { const targetKey = collectionField?.targetKey || 'id'; - const list = (field.value || []).map((option) => option[targetKey]).filter(Boolean); + const list = (field.value || []).map((option) => option?.[targetKey]).filter(Boolean); const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {}; return filter; }; + //分页 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(field.componentProps?.pageSize || 10); // 每页条数 + useEffect(() => { + setPageSize(field.componentProps?.pageSize); + }, [field.componentProps?.pageSize]); + + const paginationConfig = useMemo(() => { + const page = Math.ceil(field.value?.length / 10); + return { + current: currentPage > page ? page : currentPage, + pageSize: pageSize || 10, + total: field?.value?.length, + onChange: (page, pageSize) => { + setCurrentPage(page); + setPageSize(pageSize); + field.onInput(field.value); + }, + showSizeChanger: true, + pageSizeOptions: ['10', '20', '50', '100'], + hideOnSinglePage: false, + }; + }, [field.value?.length, pageSize, currentPage]); return (
- {/* 在这里加,是为了让 “当前对象” 的配置显示正确 */} - + {/* 在这里加,是为了让子表格中默认值的 “当前对象” 的配置显示正确 */} + { + if (!field.editable) { + return false; + } + if (allowDisassociation !== false) { + return true; + } + return record?.__isNewRecord__; + } + : false + } + pagination={paginationConfig} rowSelection={{ type: 'none', hideSelectAll: true }} footer={() => field.editable && ( <> - {field.componentProps?.allowAddnew !== false && ( + {allowAddnew !== false && ( )} - {field.componentProps?.allowSelectExistingRecord && ( + {allowSelectExistingRecord && ( )} diff --git a/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx b/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx index 906220ee8b..0cba26e021 100644 --- a/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx +++ b/packages/core/client/src/schema-component/antd/filter/FilterAction.tsx @@ -21,6 +21,7 @@ import { useProps } from '../../hooks/useProps'; import { Action, ActionProps } from '../action'; import { DatePickerProvider } from '../date-picker/DatePicker'; import { StablePopover } from '../popover'; +import { useCompile } from '../../'; export const FilterActionContext = createContext(null); FilterActionContext.displayName = 'FilterActionContext'; @@ -50,6 +51,8 @@ export const FilterAction = withDynamicSchemaProps( const [visible, setVisible] = useState(false); const { designable, dn } = useDesignable(); const fieldSchema = useFieldSchema(); + const compile = useCompile(); + const form = useMemo
(() => props.form || createForm(), []); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema @@ -58,7 +61,6 @@ export const FilterAction = withDynamicSchemaProps( const onOpenChange = useCallback((visible: boolean): void => { setVisible(visible); }, []); - return ( { await form.reset(); onReset?.(form.values); - field.title = t('Filter'); + field.title = compile(fieldSchema.title) || t('Filter'); setVisible(false); }} > diff --git a/packages/core/client/src/schema-component/antd/filter/__e2e__/FilterAction.test.ts b/packages/core/client/src/schema-component/antd/filter/__e2e__/FilterAction.test.ts index 518462d13a..f35ab6d2ba 100644 --- a/packages/core/client/src/schema-component/antd/filter/__e2e__/FilterAction.test.ts +++ b/packages/core/client/src/schema-component/antd/filter/__e2e__/FilterAction.test.ts @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { test } from '@nocobase/test/e2e'; +import { test, expect } from '@nocobase/test/e2e'; import { theDateStringShouldNotBeUTC } from './templates'; test.describe('FilterAction', () => { @@ -16,18 +16,20 @@ test.describe('FilterAction', () => { // get current date, format: YYYY-MM-DD const today = new Date().toISOString().split('T')[0]; - - // expect: should trigger a request, the request parameters contain filter: {"$and":[{"createdAt":{"$dateOn":"2024-07-10"}}]} - const requestPromise = page.waitForRequest( - // /api/users:list?pageSize=20&page=1&filter={"$and":[{"createdAt":{"$dateOn":"2024-07-10"}}]} - `/api/users:list?pageSize=20&page=1&filter=%7B%22%24and%22%3A%5B%7B%22createdAt%22%3A%7B%22%24dateOn%22%3A%22${today}%22%7D%7D%5D%7D`, - ); - await page.getByLabel('action-Filter.Action-Filter-').click(); await page.getByPlaceholder('Select date').click(); await page.getByTitle(today).click(); - await page.getByRole('button', { name: 'Submit' }).click(); + const [request] = await Promise.all([ + page.waitForRequest((request) => { + return request.url().includes('/api/users:list'); + }), - await requestPromise; + page.getByRole('button', { name: 'Submit' }).click(), + ]); + const requestUrl = request.url(); + const queryParams1 = new URLSearchParams(new URL(requestUrl).search); + const filter1 = queryParams1.get('filter'); + //请求参数符合预期 + await expect(JSON.parse(filter1)).toEqual({ $and: [{ createdAt: { $dateOn: today } }] }); }); }); diff --git a/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts b/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts index e193e2a2df..001381c5c2 100644 --- a/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts +++ b/packages/core/client/src/schema-component/antd/filter/useFilterActionProps.ts @@ -16,7 +16,7 @@ import { useBlockRequestContext } from '../../../block-provider'; import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../collection-manager'; import { mergeFilter } from '../../../filter-provider/utils'; import { useDataLoadingMode } from '../../../modules/blocks/data-blocks/details-multi/setDataLoadingModeSettingsItem'; - +import { useCompile } from '../../'; export const useGetFilterOptions = () => { const { getCollectionFields } = useCollectionManager_deprecated(); const getFilterFieldOptions = useGetFilterFieldOptions(); @@ -106,6 +106,9 @@ export const useFilterFieldOptions = (fields) => { if (!field.interface) { return; } + if (field.filterable === false) { + return; + } const fieldInterface = getInterface(field.interface); if (!fieldInterface?.filterable) { return; @@ -182,6 +185,8 @@ export const useFilterFieldProps = ({ options, service, params }) => { const { t } = useTranslation(); const field = useField(); const dataLoadingMode = useDataLoadingMode(); + const fieldSchema = useFieldSchema(); + const compile = useCompile(); return { options, @@ -205,7 +210,7 @@ export const useFilterFieldProps = ({ options, service, params }) => { if (items?.length) { field.title = t('{{count}} filter items', { count: items?.length || 0 }); } else { - field.title = t('Filter'); + field.title = compile(fieldSchema.title) || t('Filter'); } }, onReset() { @@ -222,8 +227,6 @@ export const useFilterFieldProps = ({ options, service, params }) => { { filters }, ]; - field.title = t('Filter'); - if (dataLoadingMode === 'manual') { service.params = newParams; return service.mutate(undefined); diff --git a/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx b/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx index a5700807d2..957d4d09ee 100644 --- a/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/SchemaSettingOptions.tsx @@ -30,6 +30,7 @@ export const EditTitle = () => { const fieldSchema = useFieldSchema(); const { t } = useTranslation(); const { dn } = useDesignable(); + const compile = useCompile(); const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']); return collectionField ? ( @@ -53,16 +54,16 @@ export const EditTitle = () => { } as ISchema } onSubmit={({ title }) => { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } + const result = title.trim() === '' ? collectionField?.uiSchema?.title : title; + field.title = compile(result); + fieldSchema.title = title; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + title: fieldSchema.title, + }, + }); + dn.refresh(); }} /> diff --git a/packages/core/client/src/schema-component/antd/form-item/__tests__/hooks/useSpecialCase.test.ts b/packages/core/client/src/schema-component/antd/form-item/__tests__/hooks/useSpecialCase.test.ts index ddcecc88ea..62fdcf9472 100644 --- a/packages/core/client/src/schema-component/antd/form-item/__tests__/hooks/useSpecialCase.test.ts +++ b/packages/core/client/src/schema-component/antd/form-item/__tests__/hooks/useSpecialCase.test.ts @@ -72,7 +72,7 @@ describe('isSpecialCaseField', () => { type: 'hasOne', }; const fieldSchema: any = { - default: '', + default: '{{ $context }}', }; const getCollectionField = vi.fn().mockReturnValue(null); @@ -92,7 +92,7 @@ describe('isSpecialCaseField', () => { }, }; const fieldSchema: any = { - default: '', + default: '{{ $context }}', parent: parentFieldSchema, }; @@ -116,7 +116,7 @@ describe('isSpecialCaseField', () => { }, }; const fieldSchema: any = { - default: '', + default: '{{ $context }}', parent: parentFieldSchema, }; const getCollectionField = vi.fn().mockReturnValue({ diff --git a/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts b/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts index 71a5e3b52b..3d7936c607 100644 --- a/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts +++ b/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts @@ -16,8 +16,9 @@ import { useCallback, useEffect } from 'react'; import { useRecordIndex } from '../../../../../src/record-provider'; import { useOperators } from '../../../../block-provider/CollectOperators'; import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider'; -import { InheritanceCollectionMixin, useCollection_deprecated } from '../../../../collection-manager'; +import { InheritanceCollectionMixin } from '../../../../collection-manager'; import { useCollectionRecord } from '../../../../data-source/collection-record/CollectionRecordProvider'; +import { useCollection } from '../../../../data-source/collection/CollectionProvider'; import { DataSourceManager } from '../../../../data-source/data-source/DataSourceManager'; import { useDataSourceManager } from '../../../../data-source/data-source/DataSourceManagerProvider'; import { useFlag } from '../../../../flag-provider'; @@ -40,7 +41,7 @@ const useParseDefaultValue = () => { const record = useCollectionRecord(); const { isInAssignFieldValues, isInSetDefaultValueDialog, isInFormDataTemplate, isInSubTable, isInSubForm } = useFlag() || {}; - const { getField } = useCollection_deprecated(); + const collection = useCollection(); const { isSpecialCase, setDefaultValue } = useSpecialCase(); const index = useRecordIndex(); const { type, form } = useFormBlockContext(); @@ -92,7 +93,7 @@ const useParseDefaultValue = () => { } field.loading = true; - const collectionField = !fieldSchema.name.toString().includes('.') && getField(fieldSchema.name); + const collectionField = !fieldSchema.name.toString().includes('.') && collection?.getField(fieldSchema.name); if (process.env.NODE_ENV !== 'production') { if (!collectionField) { @@ -108,9 +109,9 @@ const useParseDefaultValue = () => { fieldOperator: getOperator(fieldSchema.name), }); - // fix https://tasks.aliyun.nocobase.com/admin/ugmnj2ycfgg/popups/1qlw5c38t3b/puid/dz42x7ffr7i/filterbytk/199 if ( collectionField?.target && + collectionNameOfVariable && collectionField.target !== collectionNameOfVariable && !isInherit({ collectionName: collectionField.target, @@ -191,7 +192,7 @@ const useParseDefaultValue = () => { // 解决子表格(或子表单)中新增一行数据时,默认值不生效的问题 field.setValue(fieldSchema.default); } - }, [fieldSchema.default, localVariables, type, getOperator, dm]); + }, [fieldSchema.default, localVariables, type, getOperator, dm, collection]); }; export default useParseDefaultValue; diff --git a/packages/core/client/src/schema-component/antd/form-item/hooks/useSpecialCase.ts b/packages/core/client/src/schema-component/antd/form-item/hooks/useSpecialCase.ts index 699d97b65e..0b213e8e6c 100644 --- a/packages/core/client/src/schema-component/antd/form-item/hooks/useSpecialCase.ts +++ b/packages/core/client/src/schema-component/antd/form-item/hooks/useSpecialCase.ts @@ -101,8 +101,8 @@ export function isSpecialCaseField({ fieldSchema: Schema; getCollectionField: (name: string) => CollectionFieldOptions_deprecated; }) { - // 排除掉“当前对象”这个变量 - if (fieldSchema.default.includes('$iteration')) { + // 只针对“表格选中记录”变量有效 + if (!fieldSchema.default || !fieldSchema.default.includes('$context')) { return false; } diff --git a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx index 508aa9e3cb..b4155cdc5f 100644 --- a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx +++ b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx @@ -15,7 +15,7 @@ import { uid } from '@formily/shared'; import { ConfigProvider, Spin, theme } from 'antd'; import React, { useEffect, useMemo } from 'react'; import { useActionContext } from '..'; -import { useAttach, useComponent, useDesignable } from '../..'; +import { useAttach, useComponent } from '../..'; import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider'; import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps'; import { bindLinkageRulesToFiled } from '../../../schema-settings/LinkageRules/bindLinkageRulesToFiled'; @@ -24,6 +24,7 @@ import { useToken } from '../../../style'; import { useLocalVariables, useVariables } from '../../../variables'; import { useProps } from '../../hooks/useProps'; import { useFormBlockHeight } from './hook'; +import { getCardItemSchema } from '../../../block-provider'; export interface FormProps extends IFormLayoutProps { form?: FormilyForm; @@ -34,15 +35,27 @@ const FormComponent: React.FC = (props) => { const { form, children, ...others } = props; const field = useField(); const fieldSchema = useFieldSchema(); + const cardItemSchema = getCardItemSchema?.(fieldSchema); // TODO: component 里 useField 会与当前 field 存在偏差 const f = useAttach(form.createVoidField({ ...field.props, basePath: '' })); const height = useFormBlockHeight(); const { token } = theme.useToken(); - const { designable } = useDesignable(); + const { + layout = 'vertical', + labelAlign = 'left', + labelWidth = 120, + labelWrap = true, + } = cardItemSchema?.['x-component-props'] || {}; return ( - +
{ if (!field.title) { field.title = compile(collectionField?.uiSchema?.title); } - if (ctx?.field) { - ctx.field.added = ctx.field.added || new Set(); - ctx.field.added.add(fieldSchema.name); - } }, []); return
{props.children}
; }, diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx index deb748099a..151a4c5e85 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx @@ -9,6 +9,7 @@ import { css, cx } from '@emotion/css'; import { ArrayField } from '@formily/core'; +import { FormLayout } from '@formily/antd-v5'; import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; import { List as AntdList, Col, PaginationProps } from 'antd'; import React, { useCallback, useState } from 'react'; @@ -21,6 +22,7 @@ import { GridCardDesigner } from './GridCard.Designer'; import { GridCardItem } from './GridCard.Item'; import { useGridCardActionBarProps, useGridCardBodyHeight } from './hooks'; import { defaultColumnCount, pageSizeOptions } from './options'; +import { getCardItemSchema } from '../../../block-provider'; const rowGutter = { md: 12, @@ -159,6 +161,14 @@ const InternalGridCard = (props: GridCardProps) => { ...pagination, onChange: onPaginationChange, }; + const cardItemSchema = getCardItemSchema?.(fieldSchema); + const { + layout = 'vertical', + labelAlign = 'left', + labelWidth = 120, + labelWrap = true, + } = cardItemSchema?.['x-component-props'] || {}; + return ( { `, )} > - { - return ( -
- - - ); - }} - loading={service?.loading} - /> + + { + return ( + + + + ); + }} + loading={service?.loading} + /> + diff --git a/packages/core/client/src/schema-component/antd/grid/Grid.tsx b/packages/core/client/src/schema-component/antd/grid/Grid.tsx index 45f091d0ea..2422b184d3 100644 --- a/packages/core/client/src/schema-component/antd/grid/Grid.tsx +++ b/packages/core/client/src/schema-component/antd/grid/Grid.tsx @@ -500,7 +500,7 @@ Grid.Col = observer( width = `calc(${w}% - ${token.marginBlock}px * ${(showDivider ? cols.length + 1 : 0) / cols.length})`; } return { width }; - }, [cols?.length, schema?.['x-component-props']?.['width']]); + }, [cols?.length, schema?.['x-component-props']?.['width'], token.marginBlock]); const { isOver, setNodeRef } = useDroppable({ id: field.address.toString(), data: { @@ -549,7 +549,7 @@ Grid.Col = observer( ); }, - { displayName: 'Grid.Row' }, + { displayName: 'Grid.Col' }, ); Grid.wrap = (schema: ISchema) => { diff --git a/packages/core/client/src/schema-component/antd/index.ts b/packages/core/client/src/schema-component/antd/index.ts index 36b5d5bdbe..3dc50cb58e 100644 --- a/packages/core/client/src/schema-component/antd/index.ts +++ b/packages/core/client/src/schema-component/antd/index.ts @@ -63,5 +63,6 @@ export * from './unix-timestamp'; export * from './nanoid-input'; export * from './error-fallback'; export * from './expiresRadio'; +export * from './divider'; import './index.less'; diff --git a/packages/core/client/src/schema-component/antd/input/EllipsisWithTooltip.tsx b/packages/core/client/src/schema-component/antd/input/EllipsisWithTooltip.tsx index f9aab028aa..b7bba7be96 100644 --- a/packages/core/client/src/schema-component/antd/input/EllipsisWithTooltip.tsx +++ b/packages/core/client/src/schema-component/antd/input/EllipsisWithTooltip.tsx @@ -8,12 +8,12 @@ */ import { Popover } from 'antd'; -import React, { CSSProperties, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import React, { CSSProperties, forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react'; -const getContentWidth = (element) => { - if (element) { +const getContentWidth = (el: HTMLElement) => { + if (el) { const range = document.createRange(); - range.selectNodeContents(element); + range.selectNodeContents(el); const contentWidth = range.getBoundingClientRect().width; return contentWidth; } @@ -26,6 +26,13 @@ const ellipsisDefaultStyle: CSSProperties = { wordBreak: 'break-all', }; +const isOverflowTooltip = (el: HTMLElement) => { + if (!el) return false; + const contentWidth = getContentWidth(el); + const offsetWidth = el.offsetWidth; + return contentWidth > offsetWidth; +}; + interface IEllipsisWithTooltipProps { ellipsis: boolean; popoverContent: unknown; @@ -36,28 +43,25 @@ export const EllipsisWithTooltip = forwardRef((props: Partial { - return { - setPopoverVisible: setVisible, - }; - }); - - const isOverflowTooltip = useCallback(() => { - if (!elRef.current) return false; - const contentWidth = getContentWidth(elRef.current); - const offsetWidth = elRef.current?.offsetWidth; - return contentWidth > offsetWidth; - }, [elRef.current]); + useImperativeHandle( + ref, + () => { + return { + setPopoverVisible: setVisible, + }; + }, + [], + ); const divContent = useMemo( () => props.ellipsis ? (
{ const el = e.target as any; - const isShowTooltips = isOverflowTooltip(); + const isShowTooltips = isOverflowTooltip(elRef.current); if (isShowTooltips) { setEllipsis(el.scrollWidth >= el.clientWidth); } @@ -74,7 +78,6 @@ export const EllipsisWithTooltip = forwardRef((props: Partial - {popoverContent || props.children} + {props.popoverContent || props.children}
} > diff --git a/packages/core/client/src/schema-component/antd/input/ReadPretty.tsx b/packages/core/client/src/schema-component/antd/input/ReadPretty.tsx index 1fd7bce53a..c50cc7bfae 100644 --- a/packages/core/client/src/schema-component/antd/input/ReadPretty.tsx +++ b/packages/core/client/src/schema-component/antd/input/ReadPretty.tsx @@ -12,7 +12,7 @@ import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__'; import { useFieldSchema } from '@formily/react'; import { Image, Typography } from 'antd'; import cls from 'classnames'; -import React from 'react'; +import React, { useMemo } from 'react'; import { useCompile } from '../../hooks'; import { EllipsisWithTooltip } from './EllipsisWithTooltip'; import { HTMLEncode } from './shared'; @@ -45,6 +45,12 @@ ReadPretty.Input = (props: InputReadPrettyProps) => { const prefixCls = usePrefixCls('description-input', props); // eslint-disable-next-line react-hooks/rules-of-hooks const compile = useCompile(); + // eslint-disable-next-line react-hooks/rules-of-hooks + const content = useMemo( + () => (props.value && typeof props.value === 'object' ? JSON.stringify(props.value) : compile(props.value)), + [props.value], + ); + return (
{ > {props.addonBefore} {props.prefix} - - {props.value && typeof props.value === 'object' ? JSON.stringify(props.value) : compile(props.value)} - + {content} {props.suffix} {props.addonAfter}
@@ -80,26 +84,31 @@ ReadPretty.TextArea = (props) => { const prefixCls = usePrefixCls('description-textarea', props); // eslint-disable-next-line react-hooks/rules-of-hooks const compile = useCompile(); - const value = compile(props.value ?? ''); - const { autop = true, ellipsis, text } = props; - const html = ( -
'), - }} - /> - ); + const { autop: atop = true, ellipsis, text } = props; + + // eslint-disable-next-line react-hooks/rules-of-hooks + const content = useMemo(() => { + const value = compile(props.value ?? ''); + const html = ( +
'), + }} + /> + ); + + return ellipsis ? ( + + {text || value} + + ) : atop ? ( + html + ) : ( + value + ); + }, [atop, ellipsis, props.value, text]); - const content = ellipsis ? ( - - {text || value} - - ) : autop ? ( - html - ) : ( - value - ); return (
{ const prefixCls = usePrefixCls('description-textarea', props); // eslint-disable-next-line react-hooks/rules-of-hooks const compile = useCompile(); - const value = compile(props.value ?? ''); - const { autop = true, ellipsis } = props; - const html = ( -
- ); - const text = convertToText(value); - const content = ( - - {ellipsis ? text : html} - - ); + // eslint-disable-next-line react-hooks/rules-of-hooks + const content = useMemo(() => { + const value = compile(props.value ?? ''); + const { autop = true, ellipsis } = props; + const html = ( +
+ ); + const text = convertToText(value); + return ( + + {ellipsis ? text : html} + + ); + }, [props.value]); + return (
{ // eslint-disable-next-line react-hooks/rules-of-hooks const prefixCls = usePrefixCls('json', props); - const content = props.value != null ? JSON.stringify(props.value, null, props.space ?? 2) : ''; + // eslint-disable-next-line react-hooks/rules-of-hooks + const content = useMemo( + () => (props.value != null ? JSON.stringify(props.value, null, props.space ?? 2) : ''), + [props.space, props.value], + ); const JSONContent = ( -
+    
       {content}
     
); @@ -260,7 +272,7 @@ ReadPretty.JSON = (props: JSONTextAreaReadPrettyProps) => { if (props.ellipsis) { return ( - {content} + {content} ); } diff --git a/packages/core/client/src/schema-component/antd/list/List.tsx b/packages/core/client/src/schema-component/antd/list/List.tsx index 53355e9d2c..adbae09b52 100644 --- a/packages/core/client/src/schema-component/antd/list/List.tsx +++ b/packages/core/client/src/schema-component/antd/list/List.tsx @@ -9,6 +9,7 @@ import { css, cx } from '@emotion/css'; import { ArrayField } from '@formily/core'; +import { FormLayout } from '@formily/antd-v5'; import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; import { List as AntdList, PaginationProps, theme } from 'antd'; import React, { useCallback, useState } from 'react'; @@ -21,6 +22,7 @@ import { ListDesigner } from './List.Designer'; import { ListItem } from './List.Item'; import useStyles from './List.style'; import { useListActionBarProps, useListBlockHeight } from './hooks'; +import { getCardItemSchema } from '../../../block-provider'; const InternalList = (props) => { const { service } = useListBlockContext(); @@ -28,6 +30,7 @@ const InternalList = (props) => { const fieldSchema = useFieldSchema(); const Designer = useDesigner(); const meta = service?.data?.meta; + const { pageSize, count, hasNext, page } = meta || {}; const field = useField(); const [schemaMap] = useState(new Map()); const { wrapSSR, componentCls, hashId } = useStyles(); @@ -63,7 +66,58 @@ const InternalList = (props) => { }, [run, params], ); - + const cardItemSchema = getCardItemSchema?.(fieldSchema); + const { + layout = 'vertical', + labelAlign = 'left', + labelWidth = 120, + labelWrap = true, + } = cardItemSchema?.['x-component-props'] || {}; + const usePagination = () => { + if (!count) { + return { + onChange: onPaginationChange, + total: count || field.value?.length < pageSize || !hasNext ? pageSize * page : pageSize * page + 1, + pageSize: pageSize || 10, + current: page || 1, + showSizeChanger: true, + pageSizeOptions, + simple: true, + className: css` + .ant-pagination-simple-pager { + display: none !important; + } + `, + itemRender: (_, type, originalElement) => { + if (type === 'prev') { + return ( +
+ {originalElement}
{page}
+
+ ); + } else { + return originalElement; + } + }, + }; + } + return { + onChange: onPaginationChange, + total: count || 0, + pageSize: pageSize || 10, + current: page || 1, + showSizeChanger: true, + pageSizeOptions, + }; + }; + const paginationProps = usePagination(); return wrapSSR( { )} >
- - {field.value?.length - ? field.value.map((item, index) => { - return ( - - ); - }) - : null} - + + {field.value?.length + ? field.value.map((item, index) => { + return ( + + ); + }) + : null} + +
diff --git a/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx b/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx index c1171717dc..d36cb00f8e 100644 --- a/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx +++ b/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx @@ -228,7 +228,9 @@ describe('List', () => { await waitFor(() => { expect(screen.queryByText('ID')).toBeInTheDocument(); - expect(screen.queryByText('1')).toBeInTheDocument(); + const spanElements = screen.getAllByText('1'); + const firstSpanElement = spanElements.find((el) => el.tagName === 'SPAN'); + expect(firstSpanElement).toBeInTheDocument(); }); expect(screen.queryByText('Nickname')).toBeInTheDocument(); diff --git a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx index 93f4ee1009..f840953a82 100644 --- a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx +++ b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx @@ -9,25 +9,25 @@ import { observer, useField, useFieldSchema } from '@formily/react'; import { Input as AntdInput, Button, Space, Spin, theme } from 'antd'; +import { TextAreaProps } from 'antd/es/input'; import type { TextAreaRef } from 'antd/es/input/TextArea'; import cls from 'classnames'; -import React, { useCallback, useEffect, useState, useRef } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useGlobalTheme } from '../../../global-theme'; -import { useDesignable } from '../../hooks/useDesignable'; -import { MarkdownVoidDesigner } from './Markdown.Void.Designer'; -import { useStyles } from './style'; -import { TextAreaProps } from 'antd/es/input'; -import { useBlockHeight } from '../../hooks/useBlockSize'; -import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps'; -import { useCollectionRecord } from '../../../data-source'; -import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions'; -import { VariableSelect } from '../variable/VariableSelect'; -import { useLocalVariables, useVariables } from '../../../variables'; -import { registerQrcodeWebComponent } from './qrcode-webcom'; -import { getRenderContent } from '../../common/utils/uitls'; -import { parseMarkdown } from './util'; import { useCompile } from '../../'; +import { useCollectionRecord } from '../../../data-source'; +import { useGlobalTheme } from '../../../global-theme'; +import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps'; +import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions'; +import { useLocalVariables, useVariables } from '../../../variables'; +import { getRenderContent } from '../../common/utils/uitls'; +import { useBlockHeight } from '../../hooks/useBlockSize'; +import { useDesignable } from '../../hooks/useDesignable'; +import { VariableSelect } from '../variable/VariableSelect'; +import { MarkdownVoidDesigner } from './Markdown.Void.Designer'; +import { registerQrcodeWebComponent } from './qrcode-webcom'; +import { useStyles } from './style'; +import { parseMarkdown } from './util'; export interface MarkdownEditorProps extends Omit { scope: any[]; defaultValue?: string; @@ -37,7 +37,7 @@ export interface MarkdownEditorProps extends Omit { const MarkdownEditor = (props: MarkdownEditorProps) => { const { scope } = props; - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const [value, setValue] = useState(props.defaultValue); const inputRef = useRef(null); const [options, setOptions] = useState([]); @@ -87,7 +87,12 @@ const MarkdownEditor = (props: MarkdownEditorProps) => { {t('Syntax references')}: - + + Handlebars.js @@ -145,15 +150,16 @@ export const MarkdownVoid: any = withDynamicSchemaProps( useEffect(() => { setLoading(true); const cvtContentToHTML = async () => { - const replacedContent = await getRenderContent( - engine, - content, - compile(variables), - compile(localVariables), - parseMarkdown, - ); - - setHtml(replacedContent); + setTimeout(async () => { + const replacedContent = await getRenderContent( + engine, + content, + compile(variables), + compile(localVariables), + parseMarkdown, + ); + setHtml(replacedContent); + }); setLoading(false); }; cvtContentToHTML(); diff --git a/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx b/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx index f7237b5e1f..6a3085d08a 100644 --- a/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx +++ b/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx @@ -10,7 +10,7 @@ import { LoadingOutlined } from '@ant-design/icons'; import { connect, mapProps, mapReadPretty } from '@formily/react'; import { Input as AntdInput, Spin } from 'antd'; -import React from 'react'; +import React, { useMemo } from 'react'; import { useGlobalTheme } from '../../../global-theme'; import { ReadPretty as InputReadPretty } from '../input'; import { MarkdownVoid } from './Markdown.Void'; @@ -36,16 +36,19 @@ export const MarkdownReadPretty = (props) => { const { isDarkTheme } = useGlobalTheme(); const { wrapSSR, hashId, componentCls: className } = useStyles({ isDarkTheme }); const { html = '', loading } = useParseMarkdown(props.value); - const text = convertToText(html); + const text = useMemo(() => convertToText(html), [html]); + + if (loading) { + return wrapSSR(); + } + const value = (
); - if (loading) { - return wrapSSR(); - } + return wrapSSR(); }; diff --git a/packages/core/client/src/schema-component/antd/markdown/style.ts b/packages/core/client/src/schema-component/antd/markdown/style.ts index 0cc9353b4e..a3dae9babb 100644 --- a/packages/core/client/src/schema-component/antd/markdown/style.ts +++ b/packages/core/client/src/schema-component/antd/markdown/style.ts @@ -17,6 +17,7 @@ export const useStyles = genStyleHook('nb-markdown', (token, { isDarkTheme }) => .toHexShortString(); const defaultStyle: any = { + lineHeight: 'inherit', // default style of markdown '&.nb-markdown-default': { 'pre code.hljs': { display: 'block', overflowX: 'auto', padding: '1em' }, @@ -43,13 +44,15 @@ export const useStyles = genStyleHook('nb-markdown', (token, { isDarkTheme }) => '.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag': { fontWeight: 700, }, - '.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type': { - color: '#800', - }, + '.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type': + { + color: '#800', + }, '.hljs-section,.hljs-title': { color: '#800', fontWeight: 700 }, - '.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable': { - color: '#ab5656', - }, + '.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable': + { + color: '#ab5656', + }, '.hljs-literal': { color: '#695' }, '.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code': { color: '#397300', @@ -97,15 +100,17 @@ export const useStyles = genStyleHook('nb-markdown', (token, { isDarkTheme }) => borderRadius: token.borderRadiusSM, }, '.hljs': { color: '#adbac7', background: '#22272e' }, - '.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_': { - color: '#f47067', - }, + '.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_': + { + color: '#f47067', + }, '.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_': { color: '#dcbdfb', }, - '.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable': { - color: '#6cb6ff', - }, + '.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable': + { + color: '#6cb6ff', + }, '.hljs-meta .hljs-string,.hljs-regexp,.hljs-string': { color: '#96d0ff' }, '.hljs-built_in,.hljs-symbol': { color: '#f69d50' }, '.hljs-code,.hljs-comment,.hljs-formula': { color: '#768390' }, diff --git a/packages/core/client/src/schema-component/antd/markdown/util.ts b/packages/core/client/src/schema-component/antd/markdown/util.ts index 82eb31701f..1155adee7d 100644 --- a/packages/core/client/src/schema-component/antd/markdown/util.ts +++ b/packages/core/client/src/schema-component/antd/markdown/util.ts @@ -7,19 +7,21 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import _ from 'lodash'; import { useEffect, useState } from 'react'; -export async function parseMarkdown(text: string) { +export const parseMarkdown = _.memoize(async (text: string) => { if (!text) { return text; } const m = await import('./md'); return m.default.render(text); -} +}); export function useParseMarkdown(text: string) { const [html, setHtml] = useState(''); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); + useEffect(() => { setLoading(true); parseMarkdown(text) @@ -29,6 +31,7 @@ export function useParseMarkdown(text: string) { }) .catch((error) => console.log(error)); }, [text]); + return { html, loading }; } diff --git a/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx b/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx index 02bc320bae..85d8232bb6 100644 --- a/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx @@ -20,6 +20,7 @@ import { SchemaSettingsModalItem, SchemaSettingsRemove, SchemaSettingsSubMenu, + SchemaSettingsSwitchItem, useAPIClient, useDesignable, useURLAndHTMLSchema, @@ -373,6 +374,20 @@ export const MenuDesigner = () => { initialValues={initialValues} onSubmit={onEditSubmit} /> + { + fieldSchema['x-component-props'].hidden = !!v; + field.componentProps.hidden = !!v; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + 'x-component-props': fieldSchema['x-component-props'], + }, + }); + }} + /> { - const s = schema.properties?.[info.key]; + startTransition(() => { + const s = schema.properties?.[info.key]; - if (!s) { - return; - } - - if (mode === 'mix') { - if (s['x-component'] !== 'Menu.SubMenu') { - onSelect?.(info); - } else { - const menuItemSchema = findMenuItem(s); - if (!menuItemSchema) { - return onSelect?.(info); - } - // TODO - setLoading(true); - const keys = findKeysByUid(schema, menuItemSchema['x-uid']); - setDefaultSelectedKeys(keys); - setTimeout(() => { - setLoading(false); - }, 100); - onSelect?.({ - key: menuItemSchema.name, - item: { - props: { - schema: menuItemSchema, - }, - }, - }); + if (!s) { + return; } - } else { - onSelect?.(info); - } + + if (mode === 'mix') { + if (s['x-component'] !== 'Menu.SubMenu') { + onSelect?.(info); + } else { + const menuItemSchema = findMenuItem(s); + if (!menuItemSchema) { + return onSelect?.(info); + } + // TODO + setLoading(true); + const keys = findKeysByUid(schema, menuItemSchema['x-uid']); + setDefaultSelectedKeys(keys); + setTimeout(() => { + setLoading(false); + }, 100); + onSelect?.({ + key: menuItemSchema.name, + item: { + props: { + schema: menuItemSchema, + }, + }, + }); + } + } else { + onSelect?.(info); + } + }); }, [schema, mode, onSelect, setLoading, setDefaultSelectedKeys], ); @@ -301,11 +314,19 @@ const SideMenu = ({ }) => { const { Component, getMenuItems } = useMenuItem(); - // fix https://nocobase.height.app/T-3331/description // 使用 ref 用来防止闭包问题 const sideMenuSchemaRef = useRef(sideMenuSchema); sideMenuSchemaRef.current = sideMenuSchema; + const handleSelect = useCallback( + (info) => { + startTransition(() => { + onSelect?.(info); + }); + }, + [onSelect], + ); + const items = useMemo(() => { const result = getMenuItems(() => { return ; @@ -351,7 +372,7 @@ const SideMenu = ({ mode={'inline'} openKeys={openKeys} selectedKeys={selectedKeys} - onClick={onSelect} + onClick={handleSelect} onOpenChange={setOpenKeys} className={sideMenuClass} items={items as MenuProps['items']} @@ -507,14 +528,16 @@ const menuItemTitleStyle = { Menu.Item = observer( (props) => { const { t } = useMenuTranslation(); + const { designable } = useDesignable(); const { pushMenuItem } = useCollectMenuItems(); - const { icon, children, ...others } = props; + const { icon, children, hidden, ...others } = props; const schema = useFieldSchema(); const field = useField(); const Designer = useContext(MenuItemDesignerContext); const item = useMemo(() => { return { ...others, + hidden: designable ? false : hidden, className: menuItemClass, key: schema.name, eventKey: schema.name, @@ -599,7 +622,8 @@ const MenuURLButton = ({ href, params, icon }) => { Menu.URL = observer( (props) => { const { pushMenuItem } = useCollectMenuItems(); - const { icon, children, ...others } = props; + const { designable } = useDesignable(); + const { icon, children, hidden, ...others } = props; const schema = useFieldSchema(); const field = useField(); const Designer = useContext(MenuItemDesignerContext); @@ -612,6 +636,7 @@ Menu.URL = observer( const item = useMemo(() => { return { ...others, + hidden: designable ? false : hidden, className: menuItemClass, key: schema.name, eventKey: schema.name, @@ -625,7 +650,7 @@ Menu.URL = observer( ), }; - }, [field.title, icon, props.href, schema, JSON.stringify(props.params)]); + }, [field.title, designable, hidden, icon, props.href, schema, JSON.stringify(props.params)]); pushMenuItem(item); return null; @@ -636,9 +661,10 @@ Menu.URL = observer( Menu.SubMenu = observer( (props) => { const { t } = useMenuTranslation(); + const { designable } = useDesignable(); const { Component, getMenuItems } = useMenuItem(); const { pushMenuItem } = useCollectMenuItems(); - const { icon, children, ...others } = props; + const { icon, children, hidden, ...others } = props; const schema = useFieldSchema(); const field = useField(); const mode = useContext(MenuModeContext); @@ -646,6 +672,7 @@ Menu.SubMenu = observer( const submenu = useMemo(() => { return { ...others, + hidden: designable ? false : hidden, className: menuItemClass, key: schema.name, eventKey: schema.name, diff --git a/packages/core/client/src/schema-component/antd/page/index.ts b/packages/core/client/src/schema-component/antd/page/index.ts index ed22ee7a07..7f8883bfdb 100644 --- a/packages/core/client/src/schema-component/antd/page/index.ts +++ b/packages/core/client/src/schema-component/antd/page/index.ts @@ -12,7 +12,7 @@ export * from './FixedBlock'; export * from './FixedBlockDesignerItem'; export * from './Page'; export * from './Page.Settings'; -export { PagePopups } from './PagePopups'; +export { PagePopups, useCurrentPopupContext } from './PagePopups'; export { getPopupPathFromParams, getStoredPopupContext, storePopupContext, withSearchParams } from './pagePopupUtils'; export * from './PageTab.Settings'; export { PopupSettingsProvider, usePopupSettings } from './PopupSettingsProvider'; diff --git a/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx b/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx index 1f34e81afa..3af5690adb 100644 --- a/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx +++ b/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx @@ -145,7 +145,7 @@ export const usePopupUtils = ( const collection = useCollection(); const cm = useCollectionManager(); const association = useAssociationName(); - const { visible, setVisible } = useContext(PopupVisibleProviderContext) || { visible: false, setVisible: () => {} }; + const { visible, setVisible } = useContext(PopupVisibleProviderContext) || { visible: false, setVisible: _.noop }; const { params: popupParams } = useCurrentPopupContext(); const service = useDataBlockRequest(); const { isPopupVisibleControlledByURL } = usePopupSettings(); diff --git a/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx b/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx index 6f711c2395..5250a1b63d 100644 --- a/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx +++ b/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx @@ -21,12 +21,12 @@ describe('Pagination', () => { expect(container).toMatchInlineSnapshot(`
  • { expect(container).toMatchInlineSnapshot(`
    diff --git a/packages/core/client/src/schema-component/antd/record-picker/ReadPrettyRecordPicker.tsx b/packages/core/client/src/schema-component/antd/record-picker/ReadPrettyRecordPicker.tsx index a05ac39a1f..2e533fd72f 100644 --- a/packages/core/client/src/schema-component/antd/record-picker/ReadPrettyRecordPicker.tsx +++ b/packages/core/client/src/schema-component/antd/record-picker/ReadPrettyRecordPicker.tsx @@ -13,11 +13,9 @@ import React, { Fragment, useRef, useState } from 'react'; import { WithoutTableFieldResource } from '../../../block-provider'; // TODO: 不要使用 '../../../block-provider' 这个路径引用 BlockAssociationContext,在 Vitest 中会报错,待修复 import { BlockAssociationContext } from '../../../block-provider/BlockProvider'; -import { - CollectionProvider_deprecated, - useCollection_deprecated, - useCollectionManager_deprecated, -} from '../../../collection-manager'; +import { CollectionProvider_deprecated } from '../../../collection-manager'; +import { useCollectionManager } from '../../../data-source/collection/CollectionManagerProvider'; +import { useCollection } from '../../../data-source/collection/CollectionProvider'; import { RecordProvider, useRecord } from '../../../record-provider'; import { FormProvider } from '../../core'; import { useCompile } from '../../hooks'; @@ -44,23 +42,23 @@ export const ReadPrettyRecordPicker: React.FC = observer( const { ellipsis } = props; const fieldSchema = useFieldSchema(); const recordCtx = useRecord(); - const { getCollectionJoinField } = useCollectionManager_deprecated(); + const cm = useCollectionManager(); // value 做了转换,但 props.value 和原来 useField().value 的值不一致 // const field = useField(); const fieldNames = useFieldNames(props); const [visible, setVisible] = useState(false); - const { getField } = useCollection_deprecated(); - const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']); + const collection = useCollection(); + const collectionField = + collection?.getField(fieldSchema.name) || cm?.getCollectionField(fieldSchema?.['x-collection-field']); const [record, setRecord] = useState({}); const compile = useCompile(); const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); - const showFilePicker = isShowFilePicker(labelUiSchema); const { snapshot } = useActionContext(); const isTagsMode = fieldSchema['x-component-props']?.mode === 'tags'; const ellipsisWithTooltipRef = useRef(); - if (showFilePicker) { + if (isShowFilePicker(labelUiSchema)) { return collectionField ? : null; } diff --git a/packages/core/client/src/schema-component/antd/remote-select/ReadPretty.tsx b/packages/core/client/src/schema-component/antd/remote-select/ReadPretty.tsx index 029de4b35e..a71a36a46b 100644 --- a/packages/core/client/src/schema-component/antd/remote-select/ReadPretty.tsx +++ b/packages/core/client/src/schema-component/antd/remote-select/ReadPretty.tsx @@ -23,7 +23,7 @@ export interface RemoteSelectReadPrettyProps extends SelectReadPrettyProps { } export const ReadPretty = observer( - (props: any) => { + (props: RemoteSelectReadPrettyProps) => { const fieldNames = { ...defaultFieldNames, ...props.fieldNames }; const field = useField(); const fieldSchema = useFieldSchema(); diff --git a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx index 3102eb0427..9fb83fb75b 100644 --- a/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx +++ b/packages/core/client/src/schema-component/antd/remote-select/RemoteSelect.tsx @@ -65,6 +65,7 @@ const InternalRemoteSelect = withDynamicSchemaProps( optionFilter, dataSource: propsDataSource, toOptionsItem = (value) => value, + popupMatchSelectWidth = false, ...others } = props; const dataSource = useDataSourceKey(); @@ -86,7 +87,12 @@ const InternalRemoteSelect = withDynamicSchemaProps( const operator = useMemo(() => { if (targetField?.interface) { - return getInterface(targetField.interface)?.filterable?.operators[0].value || '$includes'; + const targetInterface = getInterface(targetField.interface); + const initialOperator = targetInterface?.filterable?.operators[0].value || '$includes'; + if (targetField.type === 'string') { + return '$includes'; + } + return initialOperator; } return '$includes'; }, [targetField]); @@ -240,7 +246,7 @@ const InternalRemoteSelect = withDynamicSchemaProps( return (
+ + { + if (active?.id !== id) { + setNodeRef(node); + } + ref(node); + }} + {...others} + className={classNames(props.className, { [className]: active && isOver })} + /> + ); }; @@ -276,11 +322,18 @@ const usePaginationProps = (pagination1, pagination2) => { [JSON.stringify({ ...pagination1, ...pagination2 })], ); const { total: totalCount, current, pageSize } = pagination || {}; + const blockProps = useDataBlockProps(); + const original = useAssociationFieldContext(); + const { components } = useContext(SchemaOptionsContext); + const C = original?.fieldSchema?.['x-component-props']?.summary?.Component || blockProps?.summary?.Component; const showTotal = useCallback( (total) => { + if (components[C]) { + return React.createElement(components[C]); + } return t('Total {{count}} items', { count: total }); }, - [t, totalCount], + [components, C, t], ); const result = useMemo(() => { if (totalCount) { @@ -333,7 +386,7 @@ const usePaginationProps = (pagination1, pagination2) => { if (!pagination2 && pagination1 === false) { return false; } - return result.total <= result.pageSize ? false : result; + return field.value?.length > 0 || result.total ? result : false; }; const headerClass = css` @@ -409,14 +462,73 @@ const HeaderWrapperComponent = (props) => { ); }; -const HeaderCellComponent = (props) => { +// Style when Hidden is enabled in table column configuration +const columnHiddenStyle = { + borderRight: 'none', + paddingLeft: 0, + paddingRight: 0, +}; + +// Style when Hidden is enabled in configuration mode +const columnOpacityStyle = { + opacity: 0.3, +}; + +const HeaderCellComponent = ({ columnHidden, ...props }) => { + const { designable } = useDesignable(); + + if (columnHidden) { + return ; + } + return + ); +}; + +const displayNone = { display: 'none' }; +const BodyCellComponent = ({ columnHidden, ...props }) => { + const { designable } = useDesignable(); + + if (columnHidden) { + return ( + + ); + } + + return ; +}; + interface TableProps { /** @deprecated */ useProps?: () => any; @@ -434,6 +546,112 @@ interface TableProps { isSubTable?: boolean; } +const InternalNocoBaseTable = React.memo( + (props: { + tableHeight: number; + SortableWrapper: React.FC<{}>; + tableSizeRefCallback: (instance: HTMLDivElement) => void; + defaultRowKey: (record: any) => any; + dataSource: any[]; + restProps: { rowSelection: any }; + paginationProps: any; + components: { + header: { wrapper: (props: any) => React.JSX.Element; cell: (props: any) => React.JSX.Element }; + body: { + wrapper: (props: any) => React.JSX.Element; + row: (props: any) => React.JSX.Element; + cell: (props: any) => React.JSX.Element; + }; + }; + onTableChange: any; + onRow: (record: any) => { onClick: (e: any) => void }; + rowClassName: (record: any) => string; + scroll: { x: string; y: number }; + columns: any[]; + expandable: { onExpand: (flag: any, record: any) => void; expandedRowKeys: any }; + field: ArrayField; + }): React.ReactElement => { + const { + tableHeight, + SortableWrapper, + tableSizeRefCallback, + defaultRowKey, + dataSource, + paginationProps, + components, + onTableChange, + onRow, + rowClassName, + scroll, + columns, + expandable, + field, + ...others + } = props; + + return ( +
+ + record.id} + dataSource={dataSource} + tableLayout="auto" + {...others} + pagination={paginationProps} + components={components} + onChange={onTableChange} + onRow={onRow} + rowClassName={rowClassName} + scroll={scroll} + columns={columns} + expandable={expandable} + /> + + {field.errors.length > 0 && ( +
+ {field.errors.map((error) => { + return error.messages.map((message) =>
{message}
); + })} +
+ )} +
+ ); + }, +); + +InternalNocoBaseTable.displayName = 'InternalNocoBaseTable'; + export const Table: any = withDynamicSchemaProps( observer((props: TableProps) => { const { token } = useToken(); @@ -456,39 +674,36 @@ export const Table: any = withDynamicSchemaProps( ...others } = { ...others1, ...others2 } as any; const field = useArrayField(others); - const columns = useTableColumns(others); const schema = useFieldSchema(); + const { size = 'middle' } = schema?.['x-component-props'] || {}; const collection = useCollection(); const isTableSelector = schema?.parent?.['x-decorator'] === 'TableSelectorProvider'; const ctx = isTableSelector ? useTableSelectorContext() : useTableBlockContext(); const { expandFlag, allIncludesChildren } = ctx; const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {})); const paginationProps = usePaginationProps(pagination1, pagination2); + const columns = useTableColumns(others, paginationProps); const [expandedKeys, setExpandesKeys] = useState(() => (expandFlag ? allIncludesChildren : [])); const [selectedRowKeys, setSelectedRowKeys] = useState(field?.data?.selectedRowKeys || []); const [selectedRow, setSelectedRow] = useState([]); const isRowSelect = rowSelection?.type !== 'none'; const defaultRowKeyMap = useRef(new Map()); - const highlightRowCss = useMemo(() => { return css` & > td { - background-color: ${token.controlItemBgActiveHover} !important; + background-color: ${token.controlItemBgActive} !important; } &:hover > td { background-color: ${token.controlItemBgActiveHover} !important; } `; - }, [token.controlItemBgActiveHover]); + }, [token.controlItemBgActive, token.controlItemBgActiveHover]); - const highlightRow = useMemo( - () => (onClickRow ? highlightRowCss : ''), - [onClickRow, token.controlItemBgActiveHover], - ); + const highlightRow = useMemo(() => (onClickRow ? highlightRowCss : ''), [highlightRowCss, onClickRow]); const onRow = useMemo(() => { if (onClickRow) { - return (record) => { + return (record, rowIndex) => { return { onClick: (e) => { if (isPortalInBody(e.target)) { @@ -496,6 +711,7 @@ export const Table: any = withDynamicSchemaProps( } onClickRow(record, setSelectedRow, selectedRow); }, + rowIndex, }; }; } @@ -556,67 +772,38 @@ export const Table: any = withDynamicSchemaProps( [JSON.stringify(rowKey), defaultRowKey], ); - const dataSourceKeys = field?.value?.map?.(getRowKey); - const memoizedDataSourceKeys = useMemo(() => dataSourceKeys, [JSON.stringify(dataSourceKeys)]); const dataSource = useMemo(() => { const value = Array.isArray(field?.value) ? field.value : []; return value.filter(Boolean); - }, [field?.value, field?.value?.length, memoizedDataSourceKeys]); - const bodyWrapperComponent = useMemo(() => { + // If we don't depend on "field?.value?.length", it will cause no response when clicking "Add new" in the SubTable + }, [field?.value, field?.value?.length]); + + const BodyWrapperComponent = useMemo(() => { return (props) => { + const onDragEndCallback = useCallback((e) => { + if (!e.active || !e.over) { + console.warn('move cancel'); + return; + } + const fromIndex = e.active?.data.current?.sortable?.index; + const toIndex = e.over?.data.current?.sortable?.index; + const from = field.value[fromIndex] || e.active; + const to = field.value[toIndex] || e.over; + void field.move(fromIndex, toIndex); + onRowDragEnd({ from, to }); + }, []); + return ( - { - if (!e.active || !e.over) { - console.warn('move cancel'); - return; - } - const fromIndex = e.active?.data.current?.sortable?.index; - const toIndex = e.over?.data.current?.sortable?.index; - const from = field.value[fromIndex] || e.active; - const to = field.value[toIndex] || e.over; - void field.move(fromIndex, toIndex); - onRowDragEnd({ from, to }); - }} - > +
); }; - }, [onRowDragEnd, field]); + }, [field, onRowDragEnd]); - const BodyCellComponent = useCallback( - (props) => { - const isIndex = props.className?.includes('selection-column'); - const { record, schema, rowIndex } = props; - const { ref, inView } = useInView({ - threshold: 0, - triggerOnce: true, - initialInView: isIndex || !!process.env.__E2E__, - skip: isIndex || !!process.env.__E2E__, - }); - const { valueMap } = useSatisfiedActionValues({ formValues: record, category: 'style', schema }); - const style = useMemo(() => Object.assign({ ...props.style }, valueMap), [props.style, valueMap]); - - // fix the problem of blank rows at the beginning of a table block - if (rowIndex < 20) { - return ( - - ); - } - - return ( - - ); - }, - [others.isSubTable], - ); + // @ts-ignore + BodyWrapperComponent.displayName = 'BodyWrapperComponent'; const components = useMemo(() => { return { @@ -625,12 +812,12 @@ export const Table: any = withDynamicSchemaProps( cell: HeaderCellComponent, }, body: { - wrapper: bodyWrapperComponent, + wrapper: BodyWrapperComponent, row: BodyRowComponent, cell: BodyCellComponent, }, }; - }, [BodyCellComponent, bodyWrapperComponent]); + }, [BodyWrapperComponent]); const memoizedRowSelection = useMemo(() => rowSelection, [JSON.stringify(rowSelection)]); @@ -762,64 +949,28 @@ export const Table: any = withDynamicSchemaProps( }; }, [expandedKeys, onExpandValue]); return ( -
- - record.id} - dataSource={dataSource} - tableLayout="auto" - {...others} - {...restProps} - loading={loading} - pagination={paginationProps} - components={components} - onChange={onTableChange} - onRow={onRow} - rowClassName={rowClassName} - scroll={scroll} - columns={columns} - expandable={expandable} - /> - - {field.errors.length > 0 && ( -
- {field.errors.map((error) => { - return error.messages.map((message) =>
{message}
); - })} -
- )} -
+ // If spinning is set to undefined, it will cause the subtable to always display loading, so we need to convert it here + + + ); }), { displayName: 'NocoBaseTable' }, diff --git a/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx b/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx index 9d04b52971..a02076adc1 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/TableBlockDesigner.tsx @@ -303,6 +303,30 @@ export const TableBlockDesigner = () => { }); }} /> + { + const schema = fieldSchema.reduceProperties((_, s) => { + if (s['x-component'] === 'TableV2') { + return s; + } + }, null); + schema['x-component-props'] = schema['x-component-props'] || {}; + schema['x-component-props']['size'] = size; + dn.emit('patch', { + schema: { + ['x-uid']: schema['x-uid'], + 'x-decorator-props': schema['x-component-props'], + }, + }); + }} + /> {supportTemplate && } {supportTemplate && ( diff --git a/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx b/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx index 9e51dad89b..9f99de017c 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/TableField.tsx @@ -10,7 +10,6 @@ import { Field } from '@formily/core'; import { observer, useField, useFieldSchema, useForm } from '@formily/react'; import React, { useEffect } from 'react'; -import { useFormBlockContext } from '../../../block-provider/FormBlockProvider'; import { useCollection_deprecated } from '../../../collection-manager'; import { useCompile } from '../../hooks'; import { ActionBar } from '../action'; @@ -22,15 +21,10 @@ export const TableField: any = observer( const field = useField(); const collectionField = getField(fieldSchema.name); const compile = useCompile(); - const ctx = useFormBlockContext(); useEffect(() => { if (!field.title) { field.title = compile(collectionField?.uiSchema?.title); } - if (ctx?.field) { - ctx.field.added = ctx.field.added || new Set(); - ctx.field.added.add(fieldSchema.name); - } }, []); useEffect(() => { field.decoratorProps.asterisk = fieldSchema.required; diff --git a/packages/core/client/src/schema-component/antd/table-v2/__tests__/Table.settings.test.tsx b/packages/core/client/src/schema-component/antd/table-v2/__tests__/Table.settings.test.tsx index 729a7cc4f8..c084df5483 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/__tests__/Table.settings.test.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/__tests__/Table.settings.test.tsx @@ -219,6 +219,21 @@ describe('Table.settings', () => { }, ], }, + { + title: 'Table size', + type: 'select', + options: [ + { + label: 'Large', + }, + { + label: 'Middle', + }, + { + label: 'Small', + }, + ], + }, { title: 'Save as template', type: 'modal', diff --git a/packages/core/client/src/schema-component/antd/table-v2/components/ColumnFieldProvider.tsx b/packages/core/client/src/schema-component/antd/table-v2/components/ColumnFieldProvider.tsx index 73bafd2d72..2632a58ad5 100644 --- a/packages/core/client/src/schema-component/antd/table-v2/components/ColumnFieldProvider.tsx +++ b/packages/core/client/src/schema-component/antd/table-v2/components/ColumnFieldProvider.tsx @@ -7,42 +7,45 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { observer, RecursionField } from '@formily/react'; -import React from 'react'; -import { useRecord } from '../../../../record-provider'; +import { RecursionField } from '@formily/react'; +import React, { useMemo } from 'react'; import { useCollection } from '../../../../data-source'; -export const ColumnFieldProvider = observer( - (props: { schema: any; basePath: any; children: any }) => { - const { schema, basePath } = props; - const record = useRecord(); - const collection = useCollection(); - const fieldSchema = schema.reduceProperties((buf, s) => { - if (s['x-component'] === 'CollectionField') { - return s; - } - return buf; - }, null); - const collectionField = fieldSchema && collection.getField(fieldSchema['x-collection-field']); +import { useRecord } from '../../../../record-provider'; - if ( - fieldSchema && - record?.__collection && - collectionField && - ['select', 'multipleSelect'].includes(collectionField.interface) - ) { - const fieldName = `${record.__collection}.${fieldSchema.name}`; - const newSchema = { - ...schema.toJSON(), - properties: { - [fieldSchema.name]: { - ...fieldSchema.toJSON(), - 'x-collection-field': fieldName, - }, +export const ColumnFieldProvider = (props: { schema: any; basePath: any; children: any }) => { + const { schema, basePath } = props; + const record = useRecord(); + const collection = useCollection(); + const fieldSchema = useMemo( + () => + schema.reduceProperties((buf, s) => { + if (s['x-component'] === 'CollectionField') { + return s; + } + return buf; + }, null), + [schema], + ); + + const collectionField = fieldSchema && collection.getField(fieldSchema['x-collection-field']); + + if ( + fieldSchema && + record?.__collection && + collectionField && + ['select', 'multipleSelect'].includes(collectionField.interface) + ) { + const fieldName = `${record.__collection}.${fieldSchema.name}`; + const newSchema = { + ...schema.toJSON(), + properties: { + [fieldSchema.name]: { + ...fieldSchema.toJSON(), + 'x-collection-field': fieldName, }, - }; - return ; - } - return props.children; - }, - { displayName: 'ColumnFieldProvider' }, -); + }, + }; + return ; + } + return props.children; +}; diff --git a/packages/core/client/src/schema-component/antd/table/Table.Array.tsx b/packages/core/client/src/schema-component/antd/table/Table.Array.tsx index b506c64bbb..272d5ce59e 100644 --- a/packages/core/client/src/schema-component/antd/table/Table.Array.tsx +++ b/packages/core/client/src/schema-component/antd/table/Table.Array.tsx @@ -65,7 +65,16 @@ const useTableColumns = () => { {/* fix https://nocobase.height.app/T-3232/description */} {/* 如果作为关系表格区块,则 parentRecordData 应该有值;如果作为普通表格使用(如数据源管理页面的表格)则应该使用 recordData,且 parentRecordData 为空 */} - + + + ); diff --git a/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx b/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx index 1e91db5d91..a9532c0fa3 100644 --- a/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx +++ b/packages/core/client/src/schema-component/antd/unix-timestamp/__tests__/UnixTimestamp.test.tsx @@ -20,11 +20,11 @@ describe('UnixTimestamp', () => { expect(container).toMatchInlineSnapshot(`
{ expect(container).toMatchInlineSnapshot(`
) : ( - {item}3 + {item} ); const content = ( @@ -357,6 +358,7 @@ export function Uploader({ rules, ...props }: UploadProps) { const uploadProps = useUploadProps(props); const beforeUpload = useBeforeUpload(rules); + console.log('----------', pendingList); useEffect(() => { if (pendingList.length) { @@ -401,6 +403,8 @@ export function Uploader({ rules, ...props }: UploadProps) { }); }, []); + const QRCodeUploader = useComponent('QRCodeUploader'); + const { mimetype: accept, size } = rules ?? {}; const sizeHint = useSizeHint(size); const selectable = @@ -441,6 +445,9 @@ export function Uploader({ rules, ...props }: UploadProps) {
+ {selectable && QRCodeUploader && ( + + )} ); } diff --git a/packages/core/client/src/schema-component/antd/upload/index.ts b/packages/core/client/src/schema-component/antd/upload/index.ts index ea7cfbdecc..cd0ff5dd7f 100644 --- a/packages/core/client/src/schema-component/antd/upload/index.ts +++ b/packages/core/client/src/schema-component/antd/upload/index.ts @@ -7,5 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -export * from './Upload'; export { attachmentFileTypes } from './shared'; +export { useUploadStyles } from './style'; + +export * from './Upload'; diff --git a/packages/core/client/src/schema-component/antd/upload/shared.ts b/packages/core/client/src/schema-component/antd/upload/shared.ts index f334502859..c95605396d 100644 --- a/packages/core/client/src/schema-component/antd/upload/shared.ts +++ b/packages/core/client/src/schema-component/antd/upload/shared.ts @@ -59,8 +59,11 @@ export class AttachmentFileTypes { */ export const attachmentFileTypes = new AttachmentFileTypes(); -export function matchMimetype(file: FileModel, type: string) { - if (file.mimetype) { +export function matchMimetype(file: FileModel | UploadFile, type: string) { + if ('originFileObj' in file) { + return match(file.type, type); + } + if ('mimetype' in file) { return match(file.mimetype, type); } if (file.url) { @@ -223,7 +226,7 @@ const Rules: Record = { type RuleFunction = (file: UploadFile, options: any) => string | null; -function validate(file, rules: Record) { +export function validate(file, rules: Record) { if (!rules) { return null; } diff --git a/packages/core/client/src/schema-component/antd/upload/style.ts b/packages/core/client/src/schema-component/antd/upload/style.ts index 7180109f40..d413dfcc69 100644 --- a/packages/core/client/src/schema-component/antd/upload/style.ts +++ b/packages/core/client/src/schema-component/antd/upload/style.ts @@ -113,3 +113,5 @@ export const useStyles = genStyleHook('upload', (token) => { }, } as any; }); + +export const useUploadStyles = useStyles; diff --git a/packages/core/client/src/schema-component/antd/variable/Input.tsx b/packages/core/client/src/schema-component/antd/variable/Input.tsx index ac2182d360..3d61b74a4e 100644 --- a/packages/core/client/src/schema-component/antd/variable/Input.tsx +++ b/packages/core/client/src/schema-component/antd/variable/Input.tsx @@ -21,6 +21,7 @@ import { useTranslation } from 'react-i18next'; import { useCompile } from '../../hooks'; import { XButton } from './XButton'; import { useStyles } from './style'; +import { Json } from '../input'; const JT_VALUE_RE = /^\s*{{\s*([^{}]+)\s*}}\s*$/; @@ -124,6 +125,14 @@ const ConstantTypes = { return null; }, }, + object: { + label: '{{t("JSON")}}', + value: 'object', + component: Json, + default() { + return {}; + }, + }, }; type UseTypeConstantType = true | (string | [string, Record])[]; @@ -480,6 +489,7 @@ export function Input(props: VariableInputProps) { margin-left: -1px; `)} type={variable ? 'primary' : 'default'} + disabled={disabled} /> )} diff --git a/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx b/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx index 258a1ef51c..f37165bef8 100644 --- a/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx +++ b/packages/core/client/src/schema-component/antd/variable/RawTextArea.tsx @@ -25,7 +25,7 @@ function setNativeInputValue(input, value) { export function RawTextArea(props): JSX.Element { const inputRef = useRef(null); - const { changeOnSelect, component: Component = Input.TextArea, fieldNames, scope, ...others } = props; + const { changeOnSelect, component: Component = Input.TextArea, fieldNames, scope, buttonClass, ...others } = props; const dataScope = typeof scope === 'function' ? scope() : scope; const [options, setOptions] = useState(dataScope ? dataScope : []); @@ -66,7 +66,7 @@ export function RawTextArea(props): JSX.Element { > { +function renderHTML(exp: string, keyLabelMap, delimiters: [string, string] = ['{{', '}}']) { + const variableRegExp = new RegExp(`${delimiters[0]}\\s*([^{}]+)\\s*${delimiters[1]}`, 'g'); + return exp.replace(variableRegExp, (_, i) => { const key = i.trim(); return createVariableTagHTML(key, keyLabelMap) ?? ''; }); @@ -210,9 +209,22 @@ function getCurrentRange(element: HTMLElement): RangeIndexes { const defaultFieldNames = { value: 'value', label: 'label' }; +function useVariablesFromValue(value: string, delimiters: [string, string] = ['{{', '}}']) { + const delimitersString = delimiters.join(' '); + return useMemo(() => { + if (!value?.trim()) { + return []; + } + const variableRegExp = new RegExp(`${delimiters[0]}\\s*([^{}]+)\\s*${delimiters[1]}`, 'g'); + const matches = value.match(variableRegExp); + return matches?.map((m) => m.replace(variableRegExp, '$1')) ?? []; + }, [value, delimitersString]); +} + export function TextArea(props) { const { wrapSSR, hashId, componentCls } = useStyles(); - const { value = '', scope, onChange, multiline = true, changeOnSelect, style, fieldNames } = props; + const { value = '', scope, onChange, changeOnSelect, style, fieldNames, delimiters = ['{{', '}}'] } = props; + const variables = useVariablesFromValue(value, delimiters); const inputRef = useRef(null); const [options, setOptions] = useState([]); const form = useForm(); @@ -222,25 +234,26 @@ export function TextArea(props) { ); const [ime, setIME] = useState(false); const [changed, setChanged] = useState(false); - const [html, setHtml] = useState(() => renderHTML(value ?? '', keyLabelMap)); + const [html, setHtml] = useState(() => renderHTML(value ?? '', keyLabelMap, delimiters)); // NOTE: e.g. [startElementIndex, startOffset, endElementIndex, endOffset] const [range, setRange] = useState<[number, number, number, number]>([-1, 0, -1, 0]); useInputStyle('ant-input'); + const delimitersString = delimiters.join(' '); useEffect(() => { - preloadOptions(scope, value) + preloadOptions(scope, variables) .then((preloaded) => { setOptions(preloaded); }) .catch(console.error); - }, [scope, value]); + }, [scope, JSON.stringify(variables)]); useEffect(() => { - setHtml(renderHTML(value ?? '', keyLabelMap)); + setHtml(renderHTML(value ?? '', keyLabelMap, delimiters)); if (!changed) { setRange([-1, 0, -1, 0]); } - }, [value, keyLabelMap]); + }, [value, keyLabelMap, delimitersString]); useEffect(() => { const { current } = inputRef; @@ -310,9 +323,9 @@ export function TextArea(props) { setChanged(true); setRange(getCurrentRange(current)); - onChange(getValue(current)); + onChange(getValue(current, delimiters)); }, - [keyLabelMap, onChange, range], + [keyLabelMap, onChange, range, delimitersString], ); const onInput = useCallback( @@ -322,9 +335,9 @@ export function TextArea(props) { } setChanged(true); setRange(getCurrentRange(currentTarget)); - onChange(getValue(currentTarget)); + onChange(getValue(currentTarget, delimiters)); }, - [ime, onChange], + [ime, onChange, delimitersString], ); const onBlur = useCallback(function ({ currentTarget }) { @@ -346,9 +359,9 @@ export function TextArea(props) { setIME(false); setChanged(true); setRange(getCurrentRange(currentTarget)); - onChange(getValue(currentTarget)); + onChange(getValue(currentTarget, delimiters)); }, - [onChange], + [onChange, delimitersString], ); const onPaste = useCallback( @@ -379,9 +392,9 @@ export function TextArea(props) { setChanged(true); pasteHTML(ev.currentTarget, sanitizedHTML); setRange(getCurrentRange(ev.currentTarget)); - onChange(getValue(ev.currentTarget)); + onChange(getValue(ev.currentTarget, delimiters)); }, - [onChange], + [onChange, delimitersString], ); const disabled = props.disabled || form.disabled; @@ -425,9 +438,10 @@ export function TextArea(props) { hashId, 'ant-input', { 'ant-input-disabled': disabled }, + // NOTE: `pre-wrap` here for avoid the ` ` (\x160) issue when paste content, we need normal space (\x32). css` overflow: auto; - white-space: ${multiline ? 'normal' : 'nowrap'}; + white-space: pre-wrap; &[placeholder]:empty::before { content: attr(placeholder); @@ -460,19 +474,14 @@ export function TextArea(props) { ); } -async function preloadOptions(scope, value: string) { +async function preloadOptions(scope, variables: string[]) { let options = [...(scope ?? [])]; - + const paths = variables.map((variable) => variable.split('.')); options = options.filter((item) => { - return !item.deprecated || value?.includes(item.value); + return !item.deprecated || paths.find((p) => p[0] === item.value); }); - // 重置正则的匹配位置 - VARIABLE_RE.lastIndex = 0; - - for (let matcher; (matcher = VARIABLE_RE.exec(value ?? '')); ) { - const keys = matcher[1].split('.'); - + for (const keys of paths) { let prevOption = null; for (let i = 0; i < keys.length; i++) { @@ -494,41 +503,38 @@ async function preloadOptions(scope, value: string) { return options; } +const textAreaReadPrettyClassName = css` + overflow: auto; + + .ant-tag { + display: inline; + line-height: 19px; + margin: 0 0.25em; + padding: 2px 7px; + border-radius: 10px; + } +`; + TextArea.ReadPretty = function ReadPretty(props): JSX.Element { - const { value } = props; + const { value, delimiters = ['{{', '}}'] } = props; const scope = typeof props.scope === 'function' ? props.scope() : props.scope; const { wrapSSR, hashId, componentCls } = useStyles(); - const [options, setOptions] = useState([]); const keyLabelMap = useMemo(() => createOptionsValueLabelMap(options), [options]); - + const html = useMemo(() => renderHTML(value ?? '', keyLabelMap, delimiters), [delimiters, keyLabelMap, value]); + const variables = useVariablesFromValue(value, delimiters); useEffect(() => { - preloadOptions(scope, value) + preloadOptions(scope, variables) .then((preloaded) => { setOptions(preloaded); }) .catch(error); - }, [scope, value]); - const html = renderHTML(value ?? '', keyLabelMap); + }, [scope, variables]); const content = wrapSSR( , ); diff --git a/packages/core/client/src/schema-component/common/utils/logic.js b/packages/core/client/src/schema-component/common/utils/logic.js index b3870fea54..c5f5b18190 100644 --- a/packages/core/client/src/schema-component/common/utils/logic.js +++ b/packages/core/client/src/schema-component/common/utils/logic.js @@ -61,7 +61,7 @@ export function getJsonLogic() { if (Array.isArray(a)) { return a.includes(b); } - return a === b; + return a == b; }, $ne: function (a, b) { return a != b; diff --git a/packages/core/client/src/schema-component/common/utils/uitls.tsx b/packages/core/client/src/schema-component/common/utils/uitls.tsx index cb281ce3b3..d23e8cf5ee 100644 --- a/packages/core/client/src/schema-component/common/utils/uitls.tsx +++ b/packages/core/client/src/schema-component/common/utils/uitls.tsx @@ -7,11 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import helpers from '@budibase/handlebars-helpers'; -import { dayjs, getPickerFormat } from '@nocobase/utils/client'; -import Handlebars from 'handlebars'; +import { dayjs, getPickerFormat, Handlebars } from '@nocobase/utils/client'; import _, { every, findIndex, some } from 'lodash'; -import url from 'url'; import { replaceVariableValue } from '../../../block-provider/hooks'; import { VariableOption, VariablesContextType } from '../../../variables/types'; import { isVariable } from '../../../variables/utils/isVariable'; @@ -170,36 +167,6 @@ const getVariablesData = (localVariables) => { }); return data; }; -const allHelpers = helpers(); - -//遍历所有 helper 并手动注册到 Handlebars -Object.keys(allHelpers).forEach(function (helperName) { - Handlebars.registerHelper(helperName, allHelpers[helperName]); -}); -// 自定义 helper 来处理对象 -Handlebars.registerHelper('json', function (context) { - return JSON.stringify(context); -}); - -//重写urlParse -Handlebars.registerHelper('urlParse', function (str) { - try { - return JSON.stringify(url.parse(str)); - } catch (error) { - return `Invalid URL: ${str}`; - } -}); - -Handlebars.registerHelper('dateFormat', (date, format, tz) => { - if (typeof tz === 'string') { - return dayjs(date).tz(tz).format(format); - } - return dayjs(date).format(format); -}); - -Handlebars.registerHelper('isNull', (value) => { - return _.isNull(value); -}); export async function getRenderContent(templateEngine, content, variables, localVariables, defaultParse) { if (content && templateEngine === 'handlebars') { diff --git a/packages/core/client/src/schema-component/hooks/useCompile.ts b/packages/core/client/src/schema-component/hooks/useCompile.ts index a73175de39..edcb9fea4a 100644 --- a/packages/core/client/src/schema-component/hooks/useCompile.ts +++ b/packages/core/client/src/schema-component/hooks/useCompile.ts @@ -19,6 +19,11 @@ interface Props { const compileCache = {}; +const hasVariable = (source: string) => { + const reg = /{{.*?}}/g; + return reg.test(source); +}; + export const useCompile = ({ noCache }: Props = { noCache: false }) => { const options = useContext(SchemaOptionsContext); const scope = useContext(SchemaExpressionScopeContext); @@ -34,13 +39,13 @@ export const useCompile = ({ noCache }: Props = { noCache: false }) => { // source is Component Object, for example: { 'x-component': "Cascader", type: "array", title: "所属地区(行政区划)" } if (source && typeof source === 'object' && !isValidElement(source)) { - shouldCompile = true; cacheKey = JSON.stringify(source); + shouldCompile = hasVariable(cacheKey); } // source is Array, for example: [{ 'title': "{{ ('Admin')}}", name: 'admin' }, { 'title': "{{ ('Root')}}", name: 'root' }] if (Array.isArray(source)) { - shouldCompile = true; + shouldCompile = hasVariable(JSON.stringify(source)); } if (shouldCompile) { diff --git a/packages/core/client/src/schema-component/hooks/useFieldTitle.ts b/packages/core/client/src/schema-component/hooks/useFieldTitle.ts index 011c44a82a..45d27020bc 100644 --- a/packages/core/client/src/schema-component/hooks/useFieldTitle.ts +++ b/packages/core/client/src/schema-component/hooks/useFieldTitle.ts @@ -10,15 +10,17 @@ import { Field } from '@formily/core'; import { useField, useFieldSchema } from '@formily/react'; import { useEffect } from 'react'; +import { useCollectionManager } from '../../data-source/collection/CollectionManagerProvider'; +import { useCollection } from '../../data-source/collection/CollectionProvider'; import { useCompile } from './useCompile'; -import { useCollection_deprecated, useCollectionManager_deprecated } from '../../collection-manager'; export const useFieldTitle = () => { const field = useField(); const fieldSchema = useFieldSchema(); - const { getField } = useCollection_deprecated(); - const { getCollectionJoinField } = useCollectionManager_deprecated(); - const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']); + const collection = useCollection(); + const cm = useCollectionManager(); + const collectionField = + collection?.getField(fieldSchema['name']) || cm?.getCollectionField(fieldSchema['x-collection-field']); const compile = useCompile(); useEffect(() => { if (!field?.title) { diff --git a/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx b/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx index 0efc2e9b0c..12ad42ae62 100644 --- a/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx +++ b/packages/core/client/src/schema-initializer/buttons/RecordBlockInitializers.tsx @@ -8,7 +8,7 @@ */ import { Schema } from '@formily/react'; -import { useCallback, useMemo } from 'react'; +import { useCallback } from 'react'; import { useActionAvailable, useCollection, @@ -283,16 +283,6 @@ const commonOptions = { name: 'filterBlocks', title: '{{t("Filter blocks")}}', type: 'itemGroup', - useVisible() { - const collection = useCollection(); - return useMemo( - () => - collection.fields.some( - (field) => canMakeAssociationBlock(field) && ['hasMany', 'belongsToMany'].includes(field.type), - ), - [collection.fields], - ); - }, children: [ { name: 'filterForm', diff --git a/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx b/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx index 15a1750db0..b1bf08c57e 100644 --- a/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx +++ b/packages/core/client/src/schema-initializer/components/assigned-field/AssignedField.tsx @@ -47,14 +47,6 @@ const InternalField: React.FC = (props) => { field.required = !!uiSchema['required']; } }; - const ctx = useFormBlockContext(); - - useEffect(() => { - if (ctx?.field) { - ctx.field.added = ctx.field.added || new Set(); - ctx.field.added.add(fieldSchema.name); - } - }); useEffect(() => { if (!uiSchema) { diff --git a/packages/core/client/src/schema-initializer/index.ts b/packages/core/client/src/schema-initializer/index.ts index 8d49ca538b..5a1b16173e 100644 --- a/packages/core/client/src/schema-initializer/index.ts +++ b/packages/core/client/src/schema-initializer/index.ts @@ -114,6 +114,8 @@ import { CollectionFieldInitializer } from '../modules/fields/initializer/Collec import { TableCollectionFieldInitializer } from '../modules/fields/initializer/TableCollectionFieldInitializer'; import { menuItemInitializer, menuItemInitializer_deprecated } from '../modules/menu/menuItemInitializer'; import { blockInitializers, blockInitializers_deprecated } from '../modules/page/BlockInitializers'; +import { DividerFormItemInitializer } from '../modules/blocks/other-blocks/divider/DividerFormItemInitializer'; + import { customFormItemInitializers, customFormItemInitializers_deprecated, @@ -182,6 +184,7 @@ export class SchemaInitializerPlugin extends Plugin { DisassociateActionInitializer, FilterActionInitializer, RefreshActionInitializer, + DividerFormItemInitializer, } as any); this.app.schemaInitializerManager.add(blockInitializers_deprecated); diff --git a/packages/core/client/src/schema-items/GeneralSchemaItems.tsx b/packages/core/client/src/schema-items/GeneralSchemaItems.tsx index 926a5e8802..6512eb018f 100644 --- a/packages/core/client/src/schema-items/GeneralSchemaItems.tsx +++ b/packages/core/client/src/schema-items/GeneralSchemaItems.tsx @@ -13,7 +13,7 @@ import _ from 'lodash'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useCollection_deprecated, useCollectionManager_deprecated } from '../collection-manager'; -import { useDesignable } from '../schema-component'; +import { useDesignable, useCompile } from '../schema-component'; import { SchemaSettingsModalItem, SchemaSettingsSwitchItem } from '../schema-settings'; import { getTempFieldState } from '../schema-settings/LinkageRules/bindLinkageRulesToFiled'; @@ -28,6 +28,7 @@ export const GeneralSchemaItems: React.FC<{ const fieldSchema = useFieldSchema(); const { t } = useTranslation(); const { dn, refresh } = useDesignable(); + const compile = useCompile(); const collectionField = getField(fieldSchema['name']) || getCollectionJoinField(fieldSchema['x-collection-field']); return ( <> @@ -52,16 +53,16 @@ export const GeneralSchemaItems: React.FC<{ } as ISchema } onSubmit={({ title }) => { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } + const result = title.trim() === '' ? collectionField?.uiSchema?.title : title; + field.title = compile(result); + fieldSchema.title = title; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + title: fieldSchema.title, + }, + }); + dn.refresh(); }} /> diff --git a/packages/core/client/src/schema-items/GeneralSettings.tsx b/packages/core/client/src/schema-items/GeneralSettings.tsx index 1fe2c2e424..6681f8211d 100644 --- a/packages/core/client/src/schema-items/GeneralSettings.tsx +++ b/packages/core/client/src/schema-items/GeneralSettings.tsx @@ -15,7 +15,7 @@ import { SchemaSettingOptions } from '../application'; import { useSchemaToolbar } from '../application/schema-toolbar'; import { useCollection_deprecated, useCollectionManager_deprecated } from '../collection-manager'; import { SchemaSettingsLinkageRules } from '../schema-settings'; -import { useIsFieldReadPretty } from '../schema-component'; +import { useIsFieldReadPretty, useCompile } from '../schema-component'; import { useCollection } from '../data-source'; export const generalSettingsItems: SchemaSettingOptions['items'] = [ @@ -25,6 +25,7 @@ export const generalSettingsItems: SchemaSettingOptions['items'] = [ useComponentProps() { const { t } = useTranslation(); const { dn } = useDesignable(); + const compile = useCompile(); const field = useField(); const fieldSchema = useFieldSchema(); const { getCollectionJoinField } = useCollectionManager_deprecated(); @@ -49,16 +50,16 @@ export const generalSettingsItems: SchemaSettingOptions['items'] = [ }, } as ISchema, onSubmit({ title }) { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } + const result = title.trim() === '' ? collectionField?.uiSchema?.title : title; + field.title = compile(result); + fieldSchema.title = title; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + title: fieldSchema.title, + }, + }); + dn.refresh(); }, }; diff --git a/packages/core/client/src/schema-settings/DataTemplates/TreeLabel.tsx b/packages/core/client/src/schema-settings/DataTemplates/TreeLabel.tsx index ca47fdd72e..a95964df92 100644 --- a/packages/core/client/src/schema-settings/DataTemplates/TreeLabel.tsx +++ b/packages/core/client/src/schema-settings/DataTemplates/TreeLabel.tsx @@ -11,7 +11,7 @@ import { Tag } from 'antd'; import React from 'react'; export const TreeNode = (props) => { - const { tag, type } = props; + const { tag, type, displayType = true } = props; const text = { reference: 'Reference', duplicate: 'Duplicate', @@ -25,7 +25,7 @@ export const TreeNode = (props) => { return (
- {tag} ({text[type]}) + {tag} {displayType ? `(${text[type]})` : ''}
); diff --git a/packages/core/client/src/schema-settings/DataTemplates/hooks/useCollectionState.ts b/packages/core/client/src/schema-settings/DataTemplates/hooks/useCollectionState.ts index ef01640ea4..9c89952c60 100644 --- a/packages/core/client/src/schema-settings/DataTemplates/hooks/useCollectionState.ts +++ b/packages/core/client/src/schema-settings/DataTemplates/hooks/useCollectionState.ts @@ -27,7 +27,13 @@ export const systemKeys = [ 'password', 'sequence', ]; -export const useCollectionState = (currentCollectionName: string) => { +/** + * + * @param currentCollectionName 数据表name + * @param displayType boolean 是否显示字段标识 + * @returns + */ +export const useCollectionState = (currentCollectionName: string, displayType = true, filterFields?) => { const { getCollectionFields, getAllCollectionsInheritChain, getCollection, getInterface } = useCollectionManager_deprecated(); const [collectionList] = useState(getCollectionList); @@ -58,6 +64,9 @@ export const useCollectionState = (currentCollectionName: string) => { if (['sort', 'password', 'sequence'].includes(field.type)) { return; } + if (filterFields && filterFields(field)) { + return; + } const node = { type: 'duplicate', tag: compile(field.uiSchema?.title) || field.name, @@ -65,7 +74,7 @@ export const useCollectionState = (currentCollectionName: string) => { const option = { ...node, role: 'button', - title: React.createElement(TreeNode, node), + title: React.createElement(TreeNode, { ...node, displayType }), key: prefix ? `${prefix}.${field.name}` : field.name, isLeaf: true, field, @@ -74,7 +83,7 @@ export const useCollectionState = (currentCollectionName: string) => { if (['belongsTo', 'belongsToMany'].includes(field.type)) { node['type'] = 'reference'; option['type'] = 'reference'; - option['title'] = React.createElement(TreeNode, { ...node, type: 'reference' }); + option['title'] = React.createElement(TreeNode, { ...node, type: 'reference', displayType }); option.isLeaf = false; option['children'] = traverseAssociations(field.target, { depth: depth + 1, @@ -115,7 +124,7 @@ export const useCollectionState = (currentCollectionName: string) => { const value = prefix ? `${prefix}.${field.name}` : field.name; return { role: 'button', - title: React.createElement(TreeNode, option), + title: React.createElement(TreeNode, { ...option, displayType }), key: value, isLeaf: false, field, @@ -134,7 +143,7 @@ export const useCollectionState = (currentCollectionName: string) => { return { ...v, role: 'button', - title: React.createElement(TreeNode, { ...v, type: v.type }), + title: React.createElement(TreeNode, { ...v, type: v.type, displayType }), children: v.children ? parseTreeData(v.children) : null, }; }); diff --git a/packages/core/client/src/schema-settings/LinkageRules/components/LinkageHeader.tsx b/packages/core/client/src/schema-settings/LinkageRules/components/LinkageHeader.tsx index 25557b0b97..29a9643ac1 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/components/LinkageHeader.tsx +++ b/packages/core/client/src/schema-settings/LinkageRules/components/LinkageHeader.tsx @@ -27,7 +27,7 @@ const LinkageRulesTitle = (props) => { const value = array?.field?.value[index]; return ( { ev.stopPropagation(); diff --git a/packages/core/client/src/schema-settings/LinkageRules/index.tsx b/packages/core/client/src/schema-settings/LinkageRules/index.tsx index ce8cbc0935..c5b97bfb27 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/index.tsx +++ b/packages/core/client/src/schema-settings/LinkageRules/index.tsx @@ -10,13 +10,13 @@ import { css } from '@emotion/css'; import { observer, useFieldSchema } from '@formily/react'; import React, { useMemo } from 'react'; -import { FormBlockContext } from '../../block-provider/FormBlockProvider'; import { useCollectionManager_deprecated } from '../../collection-manager'; import { useCollectionParentRecordData } from '../../data-source/collection-record/CollectionRecordProvider'; import { CollectionProvider } from '../../data-source/collection/CollectionProvider'; import { withDynamicSchemaProps } from '../../hoc/withDynamicSchemaProps'; import { RecordProvider } from '../../record-provider'; import { SchemaComponent, useProps } from '../../schema-component'; +import { SubFormProvider } from '../../schema-component/antd/association-field/hooks'; import { DynamicComponentProps } from '../../schema-component/antd/filter/DynamicComponent'; import { FilterContext } from '../../schema-component/antd/filter/context'; import { VariableInput, getShouldChange } from '../VariableInput/VariableInput'; @@ -31,17 +31,8 @@ export interface Props { export const FormLinkageRules = withDynamicSchemaProps( observer((props: Props) => { const fieldSchema = useFieldSchema(); - const { - options, - defaultValues, - collectionName, - form, - formBlockType, - variables, - localVariables, - record, - dynamicComponent, - } = useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema + const { options, defaultValues, collectionName, form, variables, localVariables, record, dynamicComponent } = + useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const { getAllCollectionsInheritChain } = useCollectionManager_deprecated(); const parentRecordData = useCollectionParentRecordData(); @@ -176,7 +167,8 @@ export const FormLinkageRules = withDynamicSchemaProps( ); return ( - + // 这里使用 SubFormProvider 包裹,是为了让子表格的联动规则中 “当前对象” 的配置显示正确 + @@ -184,7 +176,7 @@ export const FormLinkageRules = withDynamicSchemaProps( - + ); }), { displayName: 'FormLinkageRules' }, diff --git a/packages/core/client/src/schema-settings/LinkageRules/useActionValues.ts b/packages/core/client/src/schema-settings/LinkageRules/useActionValues.ts index ec1dc3a1af..0a4cdd7089 100644 --- a/packages/core/client/src/schema-settings/LinkageRules/useActionValues.ts +++ b/packages/core/client/src/schema-settings/LinkageRules/useActionValues.ts @@ -7,7 +7,9 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; +import { uid } from '@formily/shared'; +import { Form, onFormValuesChange } from '@formily/core'; import { useVariables, useLocalVariables } from '../../variables'; import { useFieldSchema } from '@formily/react'; import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './type'; @@ -18,11 +20,13 @@ export function useSatisfiedActionValues({ category = 'default', rules, schema, + form, }: { category: `${LinkageRuleCategory}`; formValues: Record; rules?: any; schema?: any; + form?: Form; }) { const [valueMap, setValueMap] = useState({}); const fieldSchema = useFieldSchema(); @@ -30,8 +34,7 @@ export function useSatisfiedActionValues({ const localVariables = useLocalVariables({ currentForm: { values: formValues } as any }); const localSchema = schema ?? fieldSchema; const linkageRules = rules ?? localSchema[LinkageRuleDataKeyMap[category]]; - - useEffect(() => { + const compute = useCallback(() => { if (linkageRules && formValues) { getSatisfiedValueMap({ rules: linkageRules, variables, localVariables }) .then((valueMap) => { @@ -43,6 +46,22 @@ export function useSatisfiedActionValues({ throw new Error(err.message); }); } - }, [variables, localVariables, formValues, linkageRules]); + }, [variables, localVariables, linkageRules, formValues]); + useEffect(() => { + compute(); + }, [compute]); + useEffect(() => { + if (form) { + const id = uid(); + form.addEffects(id, () => { + onFormValuesChange(() => { + compute(); + }); + }); + return () => { + form.removeEffects(id); + }; + } + }, [form, compute]); return { valueMap }; } diff --git a/packages/core/client/src/schema-settings/SchemaSettings.tsx b/packages/core/client/src/schema-settings/SchemaSettings.tsx index 84e9cb8df0..aa6d723fda 100644 --- a/packages/core/client/src/schema-settings/SchemaSettings.tsx +++ b/packages/core/client/src/schema-settings/SchemaSettings.tsx @@ -43,7 +43,7 @@ import React, { } from 'react'; import { createPortal } from 'react-dom'; import { useTranslation } from 'react-i18next'; -import { VariablesContext } from '../'; +import { SchemaSettingsItemType, VariablesContext } from '../'; import { APIClientProvider } from '../api-client/APIClientProvider'; import { useAPIClient } from '../api-client/hooks/useAPIClient'; import { ApplicationContext, LocationSearchContext, useApp, useLocationSearch } from '../application'; @@ -363,7 +363,7 @@ export const SchemaSettingsFormItemTemplate = function FormItemTemplate(props) { }; export interface SchemaSettingsItemProps extends Omit { - title: string; + title: string | ReactNode; } export const SchemaSettingsItem: FC = (props) => { const { pushMenuItem } = useCollectMenuItems(); @@ -544,7 +544,7 @@ export const SchemaSettingsCascaderItem: FC = ( }; export interface SchemaSettingsSwitchItemProps extends Omit { - title: string; + title: string | ReactNode; checked?: boolean; onChange?: (v: boolean) => void; } @@ -598,9 +598,7 @@ export const SchemaSettingsPopupItem: FC = (props) => ); }; -export interface SchemaSettingsActionModalItemProps - extends SchemaSettingsModalItemProps, - Omit { +export interface SchemaSettingsActionModalItemProps extends SchemaSettingsModalItemProps { uid?: string; initialSchema?: ISchema; schema?: ISchema; @@ -784,7 +782,7 @@ export const SchemaSettingsModalItem: FC = (props) const variableOptions = useVariables(); // 解决变量`当前对象`值在弹窗中丢失的问题 - const { formValue: subFormValue, collection: subFormCollection } = useSubFormValue(); + const { formValue: subFormValue, collection: subFormCollection, parent } = useSubFormValue(); // 解决弹窗变量丢失的问题 const popupRecordVariable = useCurrentPopupRecord(); @@ -819,7 +817,7 @@ export const SchemaSettingsModalItem: FC = (props) > - + { } }; +export const schemaSettingsLabelLayout: SchemaSettingsItemType = { + name: 'formLabelLayout', + type: 'select', + useComponentProps() { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + return { + title: t('Layout'), + value: field.componentProps?.layout || 'vertical', + options: [ + { label: t('Vertical'), value: 'vertical' }, + { label: t('Horizontal'), value: 'horizontal' }, + ], + onChange: (layout) => { + field.componentProps = field.componentProps || {}; + field.componentProps.layout = layout; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props']['layout'] = layout; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-component-props': fieldSchema['x-component-props'], + }, + }); + }, + }; + }, +}; // 是否是系统字段 export const isSystemField = (collectionField: CollectionFieldOptions_deprecated, getInterface) => { const i = getInterface?.(collectionField?.interface); diff --git a/packages/core/client/src/schema-settings/SchemaSettingsLayoutItem.tsx b/packages/core/client/src/schema-settings/SchemaSettingsLayoutItem.tsx new file mode 100644 index 0000000000..dd93fdf03f --- /dev/null +++ b/packages/core/client/src/schema-settings/SchemaSettingsLayoutItem.tsx @@ -0,0 +1,134 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import _ from 'lodash'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { SchemaSettingsModalItem } from './SchemaSettings'; +import { useDesignable } from '../schema-component/hooks/useDesignable'; + +export const Layout = { + VERTICAL: 'vertical', + HORIZONTAL: 'horizontal', +}; + +export const SchemaSettingsLayoutItem = function LayoutItem() { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn } = useDesignable(); + const { t } = useTranslation(); + + return ( + { + const componentProps = fieldSchema['x-component-props'] || {}; + componentProps.layout = layout; + componentProps.labelAlign = labelAlign; + componentProps.labelWidth = layout === 'horizontal' ? labelWidth : null; + componentProps.labelWrap = labelWrap; + fieldSchema['x-component-props'] = componentProps; + field.componentProps.layout = layout; + field.componentProps.labelAlign = labelAlign; + field.componentProps.labelWidth = labelWidth; + field.componentProps.labelWrap = labelWrap; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-component-props': fieldSchema['x-component-props'], + }, + }); + dn.refresh(); + }} + /> + ); +}; diff --git a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts index f413b0c9ba..8817f8e28f 100644 --- a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts +++ b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts @@ -68,10 +68,14 @@ import { inputNumberComponentFieldSettings } from '../modules/fields/component/I import { subformComponentFieldSettings } from '../modules/fields/component/Nester/subformComponentFieldSettings'; import { recordPickerComponentFieldSettings } from '../modules/fields/component/Picker/recordPickerComponentFieldSettings'; import { subformPopoverComponentFieldSettings } from '../modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings'; -import { selectComponentFieldSettings } from '../modules/fields/component/Select/selectComponentFieldSettings'; +import { + filterSelectComponentFieldSettings, + selectComponentFieldSettings, +} from '../modules/fields/component/Select/selectComponentFieldSettings'; import { subTablePopoverComponentFieldSettings } from '../modules/fields/component/SubTable/subTablePopoverComponentFieldSettings'; import { tagComponentFieldSettings } from '../modules/fields/component/Tag/tagComponentFieldSettings'; import { unixTimestampComponentFieldSettings } from '../modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings'; +import { dividerSettings } from '../modules/blocks/other-blocks/divider/dividerSettings'; export class SchemaSettingsPlugin extends Plugin { async load() { @@ -118,6 +122,7 @@ export class SchemaSettingsPlugin extends Plugin { // field component settings this.schemaSettingsManager.add(selectComponentFieldSettings); + this.schemaSettingsManager.add(filterSelectComponentFieldSettings); this.schemaSettingsManager.add(recordPickerComponentFieldSettings); this.schemaSettingsManager.add(subformComponentFieldSettings); this.schemaSettingsManager.add(subformPopoverComponentFieldSettings); @@ -140,5 +145,6 @@ export class SchemaSettingsPlugin extends Plugin { // this.schemaSettingsManager.add(inputURLComponentFieldSettings); this.schemaSettingsManager.add(uploadAttachmentComponentFieldSettings); this.schemaSettingsManager.add(previewComponentFieldSettings); + this.schemaSettingsManager.add(dividerSettings); } } diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts index bcdce5c2e7..933938f120 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useDateVariable.ts @@ -189,6 +189,19 @@ export const useDateVariable = ({ operator, schema, noDisabled }: Props) => { return result; }; +/** + * 变量:`日期变量`的上下文 + * @returns + */ +export const useDatetimeVariableContext = () => { + const { utc = true } = useDatePickerContext(); + const datetimeCtx = useMemo(() => getDateRanges({ shouldBeString: true, utc }), [utc]); + + return { + datetimeCtx, + }; +}; + /** * 变量:`日期变量` * @param param0 @@ -197,7 +210,6 @@ export const useDateVariable = ({ operator, schema, noDisabled }: Props) => { export const useDatetimeVariable = ({ operator, schema, noDisabled, targetFieldSchema }: Props = {}) => { const { t } = useTranslation(); const { getOperator } = useOperators(); - const { utc = true } = useDatePickerContext(); const datetimeSettings = useMemo(() => { const operatorValue = operator?.value || getOperator(targetFieldSchema?.name) || ''; @@ -348,7 +360,7 @@ export const useDatetimeVariable = ({ operator, schema, noDisabled, targetFieldS }; }, [schema?.['x-component'], targetFieldSchema]); - const datetimeCtx = useMemo(() => getDateRanges({ shouldBeString: true, utc }), [utc]); + const { datetimeCtx } = useDatetimeVariableContext(); return { datetimeSettings, diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts index 87f688d916..0cd62465e8 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useFormVariable.ts @@ -70,6 +70,28 @@ const useCurrentFormData = () => { return ctx?.data?.data?.[0] || ctx?.data?.data; }; +/** + * 变量:`当前表单` 相关的 hook + * @param param0 + * @returns + */ +export const useCurrentFormContext = ({ form: _form }: Pick = {}) => { + const { form } = useFormBlockContext(); + const formData = useCurrentFormData(); + const { isVariableParsedInOtherContext } = useFlag(); + const formInstance = _form || form; + + return { + /** 变量值 */ + currentFormCtx: + formInstance?.readPretty === false && formInstance?.values && Object.keys(formInstance?.values)?.length + ? formInstance?.values + : formData || formInstance?.values, + /** 用来判断是否可以显示`当前表单`变量 */ + shouldDisplayCurrentForm: formInstance && !formInstance.readPretty && !isVariableParsedInOtherContext, + }; +}; + /** * 变量:`当前表单` * @param param0 @@ -82,11 +104,9 @@ export const useCurrentFormVariable = ({ targetFieldSchema, form: _form, }: Props = {}) => { - // const { getActiveFieldsName } = useFormActiveFields() || {}; + const { currentFormCtx, shouldDisplayCurrentForm } = useCurrentFormContext({ form: _form }); const { t } = useTranslation(); - const { form, collectionName } = useFormBlockContext(); - const formData = useCurrentFormData(); - const { isVariableParsedInOtherContext } = useFlag(); + const { collectionName } = useFormBlockContext(); const currentFormSettings = useBaseVariable({ collectionField, uiSchema: schema, @@ -109,16 +129,12 @@ export const useCurrentFormVariable = ({ }, }); - const formInstance = _form || form; return { /** 变量配置 */ currentFormSettings, /** 变量值 */ - currentFormCtx: - formInstance?.readPretty === false && formInstance?.values && Object.keys(formInstance?.values)?.length - ? formInstance?.values - : formData || formInstance?.values, + currentFormCtx, /** 用来判断是否可以显示`当前表单`变量 */ - shouldDisplayCurrentForm: formInstance && !formInstance.readPretty && !isVariableParsedInOtherContext, + shouldDisplayCurrentForm, }; }; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useIterationVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useIterationVariable.ts index 62de51d6eb..0168620c4c 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/useIterationVariable.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useIterationVariable.ts @@ -65,6 +65,24 @@ export const useIterationVariable = ({ return result; }; +/** + * 变量:`当前对象`相关的 hook + * @returns + */ +export const useCurrentObjectContext = () => { + const { isInSubForm, isInSubTable } = useFlag() || {}; + const { formValue: currentObjectCtx, collection: collectionOfCurrentObject } = useSubFormValue(); + + return { + /** 是否显示变量 */ + shouldDisplayCurrentObject: isInSubForm || isInSubTable, + /** 变量的值 */ + currentObjectCtx, + /** 变量的 collection */ + collectionOfCurrentObject, + }; +}; + /** * 变量:`当前对象` * @param param0 @@ -84,9 +102,8 @@ export const useCurrentObjectVariable = ({ } = {}) => { // const { getActiveFieldsName } = useFormActiveFields() || {}; const collection = useCollection(); - const { formValue: currentObjectCtx, collection: collectionOfCurrentObject } = useSubFormValue(); - const { isInSubForm, isInSubTable } = useFlag() || {}; const { t } = useTranslation(); + const { shouldDisplayCurrentObject, currentObjectCtx, collectionOfCurrentObject } = useCurrentObjectContext(); const currentObjectSettings = useBaseVariable({ collectionField, uiSchema: schema, @@ -111,7 +128,7 @@ export const useCurrentObjectVariable = ({ return { /** 是否显示变量 */ - shouldDisplayCurrentObject: isInSubForm || isInSubTable, + shouldDisplayCurrentObject, /** 变量的值 */ currentObjectCtx, /** 变量的配置项 */ diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useParentIterationVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useParentIterationVariable.ts new file mode 100644 index 0000000000..fc29cc7eb4 --- /dev/null +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useParentIterationVariable.ts @@ -0,0 +1,81 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Schema } from '@formily/json-schema'; +import { useTranslation } from 'react-i18next'; +import { CollectionFieldOptions } from '../../../data-source/collection/Collection'; +import { useFlag } from '../../../flag-provider'; +import { useSubFormValue } from '../../../schema-component/antd/association-field/hooks'; +import { useBaseVariable } from './useBaseVariable'; + +export const useParentObjectContext = () => { + const { parent } = useSubFormValue(); + const { value: parentObjectCtx, collection: collectionOfParentObject } = parent || {}; + const { isInSubForm, isInSubTable } = useFlag() || {}; + + return { + /** 是否显示变量 */ + shouldDisplayParentObject: (isInSubForm || isInSubTable) && !!collectionOfParentObject, + /** 变量的值 */ + parentObjectCtx, + collectionName: collectionOfParentObject?.name, + }; +}; + +/** + * 变量:`上级对象` + * @param param0 + * @returns + */ +export const useParentObjectVariable = ({ + collectionField, + schema, + noDisabled, + targetFieldSchema, +}: { + collectionField?: CollectionFieldOptions; + schema?: any; + noDisabled?: boolean; + /** 消费变量值的字段 */ + targetFieldSchema?: Schema; +} = {}) => { + // const { getActiveFieldsName } = useFormActiveFields() || {}; + const { t } = useTranslation(); + const { shouldDisplayParentObject, parentObjectCtx, collectionName } = useParentObjectContext(); + const parentObjectSettings = useBaseVariable({ + collectionField, + uiSchema: schema, + targetFieldSchema, + maxDepth: 4, + name: '$nParentIteration', + title: t('Parent object'), + collectionName, + noDisabled, + returnFields: (fields, option) => { + return fields; + // const activeFieldsName = getActiveFieldsName?.('nester') || []; + + // return option.depth === 0 + // ? fields.filter((field) => { + // return activeFieldsName?.includes(field.name); + // }) + // : fields; + }, + }); + + return { + /** 变量的配置项 */ + parentObjectSettings, + /** 是否显示变量 */ + shouldDisplayParentObject, + /** 变量的值 */ + parentObjectCtx, + collectionName, + }; +}; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useParentPopupVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useParentPopupVariable.ts index dfe4e71782..5682abcb3f 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/useParentPopupVariable.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useParentPopupVariable.ts @@ -12,27 +12,14 @@ import { useParentPopupRecord } from '../../../modules/variable/variablesProvide import { useBaseVariable } from './useBaseVariable'; /** - * 变量:`Parent popup record` - * @param props + * 变量:`Parent popup record`的上下文 * @returns */ -export const useParentPopupVariable = (props: any = {}) => { +export const useParentPopupVariableContext = () => { const { value, title, collection } = useParentPopupRecord() || {}; const { isVariableParsedInOtherContext } = useFlag(); - const settings = useBaseVariable({ - collectionField: props.collectionField, - uiSchema: props.schema, - name: '$nParentPopupRecord', - title, - collectionName: collection?.name, - noDisabled: props.noDisabled, - targetFieldSchema: props.targetFieldSchema, - dataSource: collection?.dataSource, - }); return { - /** 变量配置 */ - settings, /** 变量值 */ parentPopupRecordCtx: value, /** 用于判断是否需要显示配置项 */ @@ -40,6 +27,38 @@ export const useParentPopupVariable = (props: any = {}) => { /** 当前记录对应的 collection name */ collectionName: collection?.name, dataSource: collection?.dataSource, + /** 不可删除 */ defaultValue: undefined, + title, + }; +}; + +/** + * 变量:`Parent popup record` + * @param props + * @returns + */ +export const useParentPopupVariable = (props: any = {}) => { + const { parentPopupRecordCtx, shouldDisplayParentPopupRecord, collectionName, dataSource, defaultValue, title } = + useParentPopupVariableContext(); + const settings = useBaseVariable({ + collectionField: props.collectionField, + uiSchema: props.schema, + name: '$nParentPopupRecord', + title, + collectionName, + noDisabled: props.noDisabled, + targetFieldSchema: props.targetFieldSchema, + dataSource, + }); + + return { + /** 变量配置 */ + settings, + parentPopupRecordCtx, + shouldDisplayParentPopupRecord, + collectionName, + dataSource, + defaultValue, }; }; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useParentRecordVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useParentRecordVariable.ts index c3c407715b..8ac8f4e9f6 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/useParentRecordVariable.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useParentRecordVariable.ts @@ -50,31 +50,17 @@ export const useParentRecordVariable = (props: Props) => { }; /** - * 变量:`上级记录` - * @param props + * 变量:`上级记录`的上下文 * @returns */ -export const useCurrentParentRecordVariable = (props: Props = {}) => { - const { t } = useTranslation(); +export const useCurrentParentRecordContext = () => { const record = useCollectionRecord(); const { name: parentCollectionName, dataSource: parentDataSource } = useParentCollection() || {}; const collection = useCollection(); const { isInSubForm, isInSubTable } = useFlag() || {}; const dataSource = parentCollectionName ? parentDataSource : collection?.dataSource; - const currentParentRecordSettings = useBaseVariable({ - collectionField: props.collectionField, - uiSchema: props.schema, - name: '$nParentRecord', - title: t('Parent record'), - collectionName: parentCollectionName || collection?.name, - noDisabled: props.noDisabled, - targetFieldSchema: props.targetFieldSchema, - dataSource, - }); - return { - currentParentRecordSettings, // 当该变量使用在区块数据范围的时候,由于某些区块(如 Table)是在 DataBlockProvider 之前解析 filter 的, // 导致此时 record.parentRecord 的值还是空的,此时正确的值应该是 record,所以在后面加了 record?.data 来防止这种情况 currentParentRecordCtx: record?.parentRecord?.data || record?.data, @@ -84,3 +70,33 @@ export const useCurrentParentRecordVariable = (props: Props = {}) => { dataSource, }; }; + +/** + * 变量:`上级记录` + * @param props + * @returns + */ +export const useCurrentParentRecordVariable = (props: Props = {}) => { + const { t } = useTranslation(); + const { currentParentRecordCtx, shouldDisplayCurrentParentRecord, collectionName, dataSource } = + useCurrentParentRecordContext(); + + const currentParentRecordSettings = useBaseVariable({ + collectionField: props.collectionField, + uiSchema: props.schema, + name: '$nParentRecord', + title: t('Parent record'), + collectionName, + noDisabled: props.noDisabled, + targetFieldSchema: props.targetFieldSchema, + dataSource, + }); + + return { + currentParentRecordSettings, + currentParentRecordCtx, + shouldDisplayCurrentParentRecord, + collectionName, + dataSource, + }; +}; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/usePopupVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/usePopupVariable.ts index ee50acf844..66f4ca9274 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/usePopupVariable.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/usePopupVariable.ts @@ -12,27 +12,14 @@ import { useCurrentPopupRecord } from '../../../modules/variable/variablesProvid import { useBaseVariable } from './useBaseVariable'; /** - * 变量:`Current popup record` - * @param props + * 变量:`Current popup record`的上下文 * @returns */ -export const usePopupVariable = (props: any = {}) => { +export const usePopupVariableContext = () => { const { value, title, collection } = useCurrentPopupRecord() || {}; const { isVariableParsedInOtherContext } = useFlag(); - const settings = useBaseVariable({ - collectionField: props.collectionField, - uiSchema: props.schema, - name: '$nPopupRecord', - title, - collectionName: collection?.name, - noDisabled: props.noDisabled, - targetFieldSchema: props.targetFieldSchema, - dataSource: collection?.dataSource, - }); return { - /** 变量配置 */ - settings, /** 变量值 */ popupRecordCtx: value, /** 用于判断是否需要显示配置项 */ @@ -40,6 +27,38 @@ export const usePopupVariable = (props: any = {}) => { /** 当前记录对应的 collection name */ collectionName: collection?.name, dataSource: collection?.dataSource, + /** 不可删除*/ defaultValue: undefined, + title, + }; +}; + +/** + * 变量:`Current popup record` + * @param props + * @returns + */ +export const usePopupVariable = (props: any = {}) => { + const { popupRecordCtx, shouldDisplayPopupRecord, collectionName, dataSource, defaultValue, title } = + usePopupVariableContext(); + const settings = useBaseVariable({ + collectionField: props.collectionField, + uiSchema: props.schema, + name: '$nPopupRecord', + title, + collectionName, + noDisabled: props.noDisabled, + targetFieldSchema: props.targetFieldSchema, + dataSource, + }); + + return { + /** 变量配置 */ + settings, + popupRecordCtx, + shouldDisplayPopupRecord, + collectionName, + dataSource, + defaultValue, }; }; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.ts index fe7c308908..29195d9604 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useRecordVariable.ts @@ -49,6 +49,29 @@ export const useRecordVariable = (props: Props) => { return currentRecordVariable; }; +/** + * 变量:`当前记录`的上下文 + * @returns + */ +export const useCurrentRecordContext = () => { + const { name: blockType } = useBlockContext() || {}; + const collection = useCollection(); + const recordData = useCollectionRecordData(); + const { formRecord, collectionName } = useFormBlockContext(); + const realCollectionName = formRecord?.data ? collectionName : collection?.name; + + return { + /** 变量值 */ + currentRecordCtx: formRecord?.data || recordData, + /** 用于判断是否需要显示配置项 */ + shouldDisplayCurrentRecord: !_.isEmpty(_.omit(recordData, ['__collectionName', '__parent'])) || !!formRecord?.data, + /** 当前记录对应的 collection name */ + collectionName: realCollectionName, + /** 块类型 */ + blockType, + }; +}; + /** * 变量:`当前记录` * @param props @@ -56,17 +79,13 @@ export const useRecordVariable = (props: Props) => { */ export const useCurrentRecordVariable = (props: Props = {}) => { const { t } = useTranslation(); - const { name: blockType } = useBlockContext() || {}; - const collection = useCollection(); - const recordData = useCollectionRecordData(); - const { formRecord, collectionName } = useFormBlockContext(); - const realCollectionName = formRecord?.data ? collectionName : collection?.name; + const { currentRecordCtx, shouldDisplayCurrentRecord, collectionName, blockType } = useCurrentRecordContext(); const currentRecordSettings = useBaseVariable({ collectionField: props.collectionField, uiSchema: props.schema, name: '$nRecord', title: t('Current record'), - collectionName: realCollectionName, + collectionName, noDisabled: props.noDisabled, targetFieldSchema: props.targetFieldSchema, deprecated: blockType === 'form', @@ -77,10 +96,10 @@ export const useCurrentRecordVariable = (props: Props = {}) => { /** 变量配置 */ currentRecordSettings, /** 变量值 */ - currentRecordCtx: formRecord?.data || recordData, + currentRecordCtx, /** 用于判断是否需要显示配置项 */ - shouldDisplayCurrentRecord: !_.isEmpty(_.omit(recordData, ['__collectionName', '__parent'])) || !!formRecord?.data, + shouldDisplayCurrentRecord, /** 当前记录对应的 collection name */ - collectionName: realCollectionName, + collectionName, }; }; diff --git a/packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts b/packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts index cf1e72ef76..541bde0b3e 100644 --- a/packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts +++ b/packages/core/client/src/schema-settings/VariableInput/hooks/useVariableOptions.ts @@ -16,6 +16,7 @@ import { useAPITokenVariable } from './useAPITokenVariable'; import { useDatetimeVariable } from './useDateVariable'; import { useCurrentFormVariable } from './useFormVariable'; import { useCurrentObjectVariable } from './useIterationVariable'; +import { useParentObjectVariable } from './useParentIterationVariable'; import { useParentPopupVariable } from './useParentPopupVariable'; import { useCurrentParentRecordVariable } from './useParentRecordVariable'; import { usePopupVariable } from './usePopupVariable'; @@ -89,6 +90,12 @@ export const useVariableOptions = ({ noDisabled, targetFieldSchema, }); + const { parentObjectSettings, shouldDisplayParentObject } = useParentObjectVariable({ + collectionField, + schema: uiSchema, + noDisabled, + targetFieldSchema, + }); const { currentRecordSettings, shouldDisplayCurrentRecord } = useCurrentRecordVariable({ schema: uiSchema, collectionField, @@ -123,6 +130,7 @@ export const useVariableOptions = ({ datetimeSettings, shouldDisplayCurrentForm && currentFormSettings, shouldDisplayCurrentObject && currentObjectSettings, + shouldDisplayParentObject && parentObjectSettings, shouldDisplayCurrentRecord && currentRecordSettings, shouldDisplayCurrentParentRecord && currentParentRecordSettings, shouldDisplayPopupRecord && popupRecordSettings, @@ -140,6 +148,8 @@ export const useVariableOptions = ({ currentFormSettings, shouldDisplayCurrentObject, currentObjectSettings, + shouldDisplayParentObject, + parentObjectSettings, shouldDisplayCurrentRecord, currentRecordSettings, shouldDisplayCurrentParentRecord, diff --git a/packages/core/client/src/schema-settings/hooks/useIsAllowToSetDefaultValue.tsx b/packages/core/client/src/schema-settings/hooks/useIsAllowToSetDefaultValue.tsx index 9408387c81..21e1d8e1a9 100644 --- a/packages/core/client/src/schema-settings/hooks/useIsAllowToSetDefaultValue.tsx +++ b/packages/core/client/src/schema-settings/hooks/useIsAllowToSetDefaultValue.tsx @@ -9,11 +9,12 @@ import { Form } from '@formily/core'; import { Schema, useFieldSchema } from '@formily/react'; -import React, { useContext, useMemo } from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; import { useFormBlockContext, useFormBlockType } from '../../block-provider/FormBlockProvider'; -import { useCollectionManager_deprecated } from '../../collection-manager/hooks/useCollectionManager_deprecated'; -import { useCollection_deprecated } from '../../collection-manager/hooks/useCollection_deprecated'; import { CollectionFieldOptions_deprecated } from '../../collection-manager/types'; +import { useCollectionManager } from '../../data-source/collection/CollectionManagerProvider'; +import { useCollection } from '../../data-source/collection/CollectionProvider'; +import { useDataSourceManager } from '../../data-source/data-source/DataSourceManagerProvider'; import { isSystemField } from '../SchemaSettings'; import { isPatternDisabled } from '../isPatternDisabled'; @@ -50,27 +51,45 @@ export const DefaultValueProvider = (props: DefaultValueProviderProps) => { }; export const useIsAllowToSetDefaultValue = ({ form, fieldSchema, collectionField }: Props = {}) => { - const { getInterface, getCollectionJoinField } = useCollectionManager_deprecated(); - const { getField } = useCollection_deprecated(); + const dm = useDataSourceManager(); + const cm = useCollectionManager(); + const collection = useCollection(); const { form: innerForm } = useFormBlockContext(); const innerFieldSchema = useFieldSchema(); const { type } = useFormBlockType(); const { isAllowToSetDefaultValue = _isAllowToSetDefaultValue } = useContext(DefaultValueContext) || {}; - const innerCollectionField = - getField(innerFieldSchema['name']) || getCollectionJoinField(innerFieldSchema['x-collection-field']); - return { - isAllowToSetDefaultValue: (isSubTableColumn?: boolean) => { - return isAllowToSetDefaultValue({ - collectionField: collectionField || innerCollectionField, - getInterface, - form: form || innerForm, - formBlockType: type, - fieldSchema: fieldSchema || innerFieldSchema, - isSubTableColumn, - }); - }, + const result = { + isAllowToSetDefaultValue: useCallback( + (isSubTableColumn?: boolean) => { + const innerCollectionField = + collection.getField(innerFieldSchema['name']) || + cm.getCollectionField(innerFieldSchema['x-collection-field']); + + return isAllowToSetDefaultValue({ + collectionField: collectionField || innerCollectionField, + getInterface: dm?.collectionFieldInterfaceManager.getFieldInterface.bind(dm?.collectionFieldInterfaceManager), + form: form || innerForm, + formBlockType: type, + fieldSchema: fieldSchema || innerFieldSchema, + isSubTableColumn, + }); + }, + [ + cm, + collection, + collectionField, + dm?.collectionFieldInterfaceManager, + fieldSchema, + form, + innerFieldSchema, + innerForm, + isAllowToSetDefaultValue, + type, + ], + ), }; + return result; }; export const interfacesOfUnsupportedDefaultValue = [ diff --git a/packages/core/client/src/schema-settings/index.ts b/packages/core/client/src/schema-settings/index.ts index 1a67832736..0202ecff16 100644 --- a/packages/core/client/src/schema-settings/index.ts +++ b/packages/core/client/src/schema-settings/index.ts @@ -25,6 +25,7 @@ export * from './setTheDataScopeSchemaSettingsItem'; export * from './SchemaSettingsRenderEngine'; export * from './hooks/useGetAriaLabelOfDesigner'; export * from './hooks/useIsAllowToSetDefaultValue'; +export * from './SchemaSettingsLayoutItem'; export { default as useParseDataScopeFilter } from './hooks/useParseDataScopeFilter'; export * from './isPatternDisabled'; export { SchemaSettingsPlugin } from './SchemaSettingsPlugin'; diff --git a/packages/core/client/src/user/ChangePassword.tsx b/packages/core/client/src/user/ChangePassword.tsx index c6837ff799..61ede07f95 100644 --- a/packages/core/client/src/user/ChangePassword.tsx +++ b/packages/core/client/src/user/ChangePassword.tsx @@ -15,6 +15,7 @@ import { useTranslation } from 'react-i18next'; import { ActionContextProvider, DropdownVisibleContext, SchemaComponent, useActionContext } from '../'; import { useAPIClient } from '../api-client'; import { useNavigate } from 'react-router-dom'; +import { useSystemSettings } from '../'; const useCloseAction = () => { const { setVisible } = useActionContext(); @@ -53,6 +54,9 @@ const schema: ISchema = { [uid()]: { 'x-decorator': 'Form', 'x-component': 'Action.Drawer', + 'x-component-props': { + zIndex: 10000, + }, type: 'void', title: '{{t("Change password")}}', properties: { @@ -129,8 +133,10 @@ export const useChangePassword = () => { const ctx = useContext(DropdownVisibleContext); const [visible, setVisible] = useState(false); const { t } = useTranslation(); + const { data } = useSystemSettings(); + const { enableChangePassword } = data?.data || {}; - return useMemo(() => { + const result = useMemo(() => { return { key: 'password', eventKey: 'ChangePassword', @@ -150,4 +156,9 @@ export const useChangePassword = () => { ), }; }, [visible]); + if (enableChangePassword === false) { + return null; + } + + return result; }; diff --git a/packages/core/client/src/user/CurrentUser.tsx b/packages/core/client/src/user/CurrentUser.tsx index ee369aa697..bb3d6cd326 100644 --- a/packages/core/client/src/user/CurrentUser.tsx +++ b/packages/core/client/src/user/CurrentUser.tsx @@ -125,7 +125,7 @@ export const SettingsMenu: React.FC<{ }, editProfile, changePassword, - { + (editProfile || changePassword) && { key: 'divider_2', type: 'divider', }, @@ -139,8 +139,12 @@ export const SettingsMenu: React.FC<{ key: 'signout', label: t('Sign out'), onClick: async () => { - await api.auth.signOut(); - navigate(`/signin?redirect=${encodeURIComponent(redirectUrl)}`); + const { data } = await api.auth.signOut(); + if (data?.data?.redirect) { + window.location.href = data.data.redirect; + } else { + navigate(`/signin?redirect=${encodeURIComponent(redirectUrl)}`); + } }, }, ]; diff --git a/packages/core/client/src/user/EditProfile.tsx b/packages/core/client/src/user/EditProfile.tsx index 9ac1849aec..b2e24e979b 100644 --- a/packages/core/client/src/user/EditProfile.tsx +++ b/packages/core/client/src/user/EditProfile.tsx @@ -19,6 +19,7 @@ import { useActionContext, useCurrentUserContext, useRequest, + useSystemSettings, } from '../'; import { useAPIClient } from '../api-client'; @@ -71,6 +72,9 @@ const schema: ISchema = { useValues: '{{ useCurrentUserValues }}', }, 'x-component': 'Action.Drawer', + 'x-component-props': { + zIndex: 10000, + }, type: 'void', title: '{{t("Edit profile")}}', properties: { @@ -79,6 +83,11 @@ const schema: ISchema = { title: "{{t('Nickname')}}", 'x-decorator': 'FormItem', 'x-component': 'Input', + 'x-reactions': (field) => { + if (field.initialValue) { + field.disabled = true; + } + }, }, username: { type: 'string', @@ -87,6 +96,11 @@ const schema: ISchema = { 'x-component': 'Input', 'x-validator': { username: true }, required: true, + 'x-reactions': (field) => { + if (field.initialValue) { + field.disabled = true; + } + }, }, email: { type: 'string', @@ -94,12 +108,22 @@ const schema: ISchema = { 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-validator': 'email', + 'x-reactions': (field) => { + if (field.initialValue) { + field.disabled = true; + } + }, }, phone: { type: 'string', title: '{{t("Phone")}}', 'x-decorator': 'FormItem', 'x-component': 'Input', + 'x-reactions': (field) => { + if (field.initialValue) { + field.disabled = true; + } + }, }, footer: { 'x-component': 'Action.Drawer.Footer', @@ -131,8 +155,9 @@ export const useEditProfile = () => { const ctx = useContext(DropdownVisibleContext); const [visible, setVisible] = useState(false); const { t } = useTranslation(); - - return useMemo(() => { + const { data } = useSystemSettings(); + const { enableEditProfile } = data?.data || {}; + const result = useMemo(() => { return { key: 'profile', eventKey: 'EditProfile', @@ -155,4 +180,8 @@ export const useEditProfile = () => { ), }; }, [visible]); + if (enableEditProfile === false) { + return null; + } + return result; }; diff --git a/packages/core/client/src/variables/hooks/useLocalVariables.tsx b/packages/core/client/src/variables/hooks/useLocalVariables.tsx index 962ea993d8..cd909c936d 100644 --- a/packages/core/client/src/variables/hooks/useLocalVariables.tsx +++ b/packages/core/client/src/variables/hooks/useLocalVariables.tsx @@ -11,13 +11,14 @@ import { Form } from '@formily/core'; import { useMemo } from 'react'; import { useCollection_deprecated } from '../../collection-manager'; import { useBlockCollection } from '../../schema-settings/VariableInput/hooks/useBlockCollection'; -import { useDatetimeVariable } from '../../schema-settings/VariableInput/hooks/useDateVariable'; -import { useCurrentFormVariable } from '../../schema-settings/VariableInput/hooks/useFormVariable'; -import { useCurrentObjectVariable } from '../../schema-settings/VariableInput/hooks/useIterationVariable'; -import { useParentPopupVariable } from '../../schema-settings/VariableInput/hooks/useParentPopupVariable'; -import { useCurrentParentRecordVariable } from '../../schema-settings/VariableInput/hooks/useParentRecordVariable'; -import { usePopupVariable } from '../../schema-settings/VariableInput/hooks/usePopupVariable'; -import { useCurrentRecordVariable } from '../../schema-settings/VariableInput/hooks/useRecordVariable'; +import { useDatetimeVariableContext } from '../../schema-settings/VariableInput/hooks/useDateVariable'; +import { useCurrentFormContext } from '../../schema-settings/VariableInput/hooks/useFormVariable'; +import { useCurrentObjectContext } from '../../schema-settings/VariableInput/hooks/useIterationVariable'; +import { useParentObjectContext } from '../../schema-settings/VariableInput/hooks/useParentIterationVariable'; +import { useParentPopupVariableContext } from '../../schema-settings/VariableInput/hooks/useParentPopupVariable'; +import { useCurrentParentRecordContext } from '../../schema-settings/VariableInput/hooks/useParentRecordVariable'; +import { usePopupVariableContext } from '../../schema-settings/VariableInput/hooks/usePopupVariable'; +import { useCurrentRecordContext } from '../../schema-settings/VariableInput/hooks/useRecordVariable'; import { VariableOption } from '../types'; import useContextVariable from './useContextVariable'; @@ -27,27 +28,32 @@ interface Props { } const useLocalVariables = (props?: Props) => { - const { currentObjectCtx, shouldDisplayCurrentObject } = useCurrentObjectVariable(); - const { currentRecordCtx, collectionName: collectionNameOfRecord } = useCurrentRecordVariable(); + const { + parentObjectCtx, + shouldDisplayParentObject, + collectionName: collectionNameOfParentObject, + } = useParentObjectContext(); + const { currentObjectCtx, shouldDisplayCurrentObject } = useCurrentObjectContext(); + const { currentRecordCtx, collectionName: collectionNameOfRecord } = useCurrentRecordContext(); const { currentParentRecordCtx, collectionName: collectionNameOfParentRecord, dataSource: currentParentRecordDataSource, - } = useCurrentParentRecordVariable(); + } = useCurrentParentRecordContext(); const { popupRecordCtx, collectionName: collectionNameOfPopupRecord, dataSource: popupDataSource, defaultValue: defaultValueOfPopupRecord, - } = usePopupVariable(); + } = usePopupVariableContext(); const { parentPopupRecordCtx, collectionName: collectionNameOfParentPopupRecord, dataSource: parentPopupDataSource, defaultValue: defaultValueOfParentPopupRecord, - } = useParentPopupVariable(); - const { datetimeCtx } = useDatetimeVariable(); - const { currentFormCtx } = useCurrentFormVariable({ form: props?.currentForm }); + } = useParentPopupVariableContext(); + const { datetimeCtx } = useDatetimeVariableContext(); + const { currentFormCtx } = useCurrentFormContext({ form: props?.currentForm }); const { name: currentCollectionName } = useCollection_deprecated(); const contextVariable = useContextVariable(); let { name } = useBlockCollection(); @@ -134,6 +140,11 @@ const useLocalVariables = (props?: Props) => { ctx: currentObjectCtx, collectionName: currentCollectionName, }, + shouldDisplayParentObject && { + name: '$nParentIteration', + ctx: parentObjectCtx, + collectionName: collectionNameOfParentObject, + }, ] as VariableOption[] ).filter(Boolean); }, [ @@ -154,6 +165,9 @@ const useLocalVariables = (props?: Props) => { currentCollectionName, defaultValueOfPopupRecord, defaultValueOfParentPopupRecord, + shouldDisplayParentObject, + parentObjectCtx, + collectionNameOfParentObject, contextVariable, ]); // 尽量保持返回的值不变,这样可以减少接口的请求次数,因为关系字段会缓存到变量的 ctx 中 }; diff --git a/packages/core/client/src/variables/utils/getAction.tsx b/packages/core/client/src/variables/utils/getAction.tsx index 7910971d2b..94dfc985c8 100644 --- a/packages/core/client/src/variables/utils/getAction.tsx +++ b/packages/core/client/src/variables/utils/getAction.tsx @@ -12,6 +12,7 @@ const TYPE_TO_ACTION = { belongsTo: 'get', hasOne: 'get', belongsToMany: 'list?pageSize=9999', + belongsToArray: 'get', }; export const getAction = (type: string) => { if (process.env.NODE_ENV !== 'production' && !(type in TYPE_TO_ACTION)) { diff --git a/packages/core/create-nocobase-app/README.md b/packages/core/create-nocobase-app/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/create-nocobase-app/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/create-nocobase-app/package.json b/packages/core/create-nocobase-app/package.json index d038578542..f517c52d0e 100755 --- a/packages/core/create-nocobase-app/package.json +++ b/packages/core/create-nocobase-app/package.json @@ -1,11 +1,11 @@ { "name": "create-nocobase-app", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "src/index.js", "license": "AGPL-3.0", "dependencies": { "@umijs/utils": "3.5.20", - "axios": "^0.26.1", + "axios": "^1.7.0", "chalk": "^4.1.1", "commander": "^9.2.0", "tar": "6.1.11" diff --git a/packages/core/create-nocobase-app/src/generator.js b/packages/core/create-nocobase-app/src/generator.js index df185933ce..260cbc308f 100644 --- a/packages/core/create-nocobase-app/src/generator.js +++ b/packages/core/create-nocobase-app/src/generator.js @@ -112,6 +112,7 @@ class AppGenerator extends Generator { envs.push(`DB_USER=${env.DB_USER || ''}`); envs.push(`DB_PASSWORD=${env.DB_PASSWORD || ''}`); break; + case 'kingbase': case 'postgres': if (!allDbDialect) { dependencies.push(`"pg": "^8.7.3"`); @@ -125,7 +126,7 @@ class AppGenerator extends Generator { break; } - const keys = ['PLUGIN_PACKAGE_PREFIX', 'DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD', 'DB_STORAGE']; + const keys = ['DB_HOST', 'DB_PORT', 'DB_DATABASE', 'DB_USER', 'DB_PASSWORD', 'DB_STORAGE']; for (const key in env) { if (keys.includes(key)) { diff --git a/packages/core/create-nocobase-app/templates/app/package.json.tpl b/packages/core/create-nocobase-app/templates/app/package.json.tpl index 5cd4c484e5..c1beaf112d 100644 --- a/packages/core/create-nocobase-app/templates/app/package.json.tpl +++ b/packages/core/create-nocobase-app/templates/app/package.json.tpl @@ -25,7 +25,8 @@ "cytoscape": "3.28.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", - "antd": "5.12.8" + "antd": "5.12.8", + "rollup": "4.24.0" }, "dependencies": { "@nocobase/cli": "{{{version}}}", diff --git a/packages/core/data-source-manager/README.md b/packages/core/data-source-manager/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/data-source-manager/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/data-source-manager/package.json b/packages/core/data-source-manager/package.json index f1b37c4978..365a448e85 100644 --- a/packages/core/data-source-manager/package.json +++ b/packages/core/data-source-manager/package.json @@ -1,16 +1,16 @@ { "name": "@nocobase/data-source-manager", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/actions": "1.4.0-alpha", - "@nocobase/cache": "1.4.0-alpha", - "@nocobase/database": "1.4.0-alpha", - "@nocobase/resourcer": "1.4.0-alpha", - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/actions": "1.4.0-alpha.11", + "@nocobase/cache": "1.4.0-alpha.11", + "@nocobase/database": "1.4.0-alpha.11", + "@nocobase/resourcer": "1.4.0-alpha.11", + "@nocobase/utils": "1.4.0-alpha.11", "@types/jsonwebtoken": "^8.5.8", "jsonwebtoken": "^8.5.1" }, diff --git a/packages/core/data-source-manager/src/collection.ts b/packages/core/data-source-manager/src/collection.ts index c3d7a592cb..d4ed2c870d 100644 --- a/packages/core/data-source-manager/src/collection.ts +++ b/packages/core/data-source-manager/src/collection.ts @@ -76,6 +76,15 @@ export class Collection implements ICollection { return this.fields.get(name); } + getFieldByField(field: string): IField { + for (const item of this.fields.values()) { + if (item.options.field === field) { + return item; + } + } + return null; + } + getFields() { return [...this.fields.values()]; } diff --git a/packages/core/data-source-manager/src/data-source-manager.ts b/packages/core/data-source-manager/src/data-source-manager.ts index 25551e7d85..a8b141d9e9 100644 --- a/packages/core/data-source-manager/src/data-source-manager.ts +++ b/packages/core/data-source-manager/src/data-source-manager.ts @@ -84,17 +84,20 @@ export class DataSourceManager { } middleware() { - return async (ctx, next) => { + const self = this; + + return async function dataSourceManager(ctx, next) { const name = ctx.get('x-data-source') || 'main'; - if (!this.dataSources.has(name)) { + if (!self.dataSources.has(name)) { ctx.throw(`data source ${name} does not exist`); } - const ds = this.dataSources.get(name); + const ds = self.dataSources.get(name); ctx.dataSource = ds; - return ds.middleware(this.middlewares)(ctx, next); + const composedFn = ds.middleware(self.middlewares); + return composedFn(ctx, next); }; } diff --git a/packages/core/data-source-manager/src/data-source.ts b/packages/core/data-source-manager/src/data-source.ts index 84ce8bdb29..8a45cac8f9 100644 --- a/packages/core/data-source-manager/src/data-source.ts +++ b/packages/core/data-source-manager/src/data-source.ts @@ -14,13 +14,20 @@ import compose from 'koa-compose'; import { loadDefaultActions } from './load-default-actions'; import { ICollectionManager } from './types'; import { Logger } from '@nocobase/logger'; +import { wrapMiddlewareWithLogging } from '@nocobase/utils'; export type DataSourceOptions = any; +export type LoadingProgress = { + total: number; + loaded: number; +}; + export abstract class DataSource extends EventEmitter { public collectionManager: ICollectionManager; public resourceManager: ResourceManager; public acl: ACL; + logger: Logger; constructor(protected options: DataSourceOptions) { @@ -28,8 +35,10 @@ export abstract class DataSource extends EventEmitter { this.init(options); } - setLogger(logger: Logger) { - this.logger = logger; + _sqlLogger: Logger; + + get sqlLogger() { + return this._sqlLogger || this.logger; } get name() { @@ -40,6 +49,14 @@ export abstract class DataSource extends EventEmitter { return Promise.resolve(true); } + setLogger(logger: Logger) { + this.logger = logger; + } + + setSqlLogger(logger: Logger) { + this._sqlLogger = logger; + } + init(options: DataSourceOptions = {}) { this.acl = this.createACL(); @@ -63,6 +80,7 @@ export abstract class DataSource extends EventEmitter { for (const [fn, options] of middlewares) { this.resourceManager.use(fn, options); } + this['_used'] = true; } @@ -75,7 +93,9 @@ export abstract class DataSource extends EventEmitter { return this.collectionManager.getRepository(resourceName, resourceOf); }; - return compose([this.collectionToResourceMiddleware(), this.resourceManager.middleware()])(ctx, next); + const middlewares = [this.collectionToResourceMiddleware(), this.resourceManager.middleware()]; + + return compose(middlewares.map((fn) => wrapMiddlewareWithLogging(fn)))(ctx, next); }; } @@ -91,21 +111,26 @@ export abstract class DataSource extends EventEmitter { return null; } + emitLoadingProgress(progress: LoadingProgress) { + this.emit('loadingProgress', progress); + } + async load(options: any = {}) {} async close() {} abstract createCollectionManager(options?: any): ICollectionManager; protected collectionToResourceMiddleware() { - return async (ctx, next) => { + const self = this; + return async function collectionToResource(ctx, next) { const params = parseRequest( { path: ctx.request.path, method: ctx.request.method, }, { - prefix: this.resourceManager.options.prefix, - accessors: this.resourceManager.options.accessors, + prefix: self.resourceManager.options.prefix, + accessors: self.resourceManager.options.accessors, }, ); if (!params) { @@ -113,7 +138,7 @@ export abstract class DataSource extends EventEmitter { } const resourceName = getNameByParams(params); // 如果资源名称未被定义 - if (this.resourceManager.isDefined(resourceName)) { + if (self.resourceManager.isDefined(resourceName)) { return next(); } @@ -121,11 +146,11 @@ export abstract class DataSource extends EventEmitter { const collectionName = splitResult[0]; - if (!this.collectionManager.hasCollection(collectionName)) { + if (!self.collectionManager.hasCollection(collectionName)) { return next(); } - this.resourceManager.define({ + self.resourceManager.define({ name: resourceName, }); diff --git a/packages/core/data-source-manager/src/types.ts b/packages/core/data-source-manager/src/types.ts index 5a9b7cef85..3609297ea0 100644 --- a/packages/core/data-source-manager/src/types.ts +++ b/packages/core/data-source-manager/src/types.ts @@ -65,6 +65,8 @@ export interface ICollection { getField(name: string): IField; + getFieldByField(field: string): IField; + [key: string]: any; unavailableActions?: () => string[]; diff --git a/packages/core/database/README.md b/packages/core/database/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/database/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/database/package.json b/packages/core/database/package.json index a3bd760c5e..9deebd1b72 100644 --- a/packages/core/database/package.json +++ b/packages/core/database/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/database", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "main": "./lib/index.js", "types": "./lib/index.d.ts", "license": "AGPL-3.0", "dependencies": { - "@nocobase/logger": "1.4.0-alpha", - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/logger": "1.4.0-alpha.11", + "@nocobase/utils": "1.4.0-alpha.11", "chalk": "^4.1.1", "cron-parser": "4.4.0", "dayjs": "^1.11.8", diff --git a/packages/core/database/src/__tests__/collection.test.ts b/packages/core/database/src/__tests__/collection.test.ts index 0da1bf3a78..81b719bd9d 100644 --- a/packages/core/database/src/__tests__/collection.test.ts +++ b/packages/core/database/src/__tests__/collection.test.ts @@ -346,6 +346,7 @@ describe('collection sync', () => { const model = collection.model; await collection.sync(); + if (db.options.underscored) { const tableFields = await (model).queryInterface.describeTable(`${db.getTablePrefix()}posts_tags`); expect(tableFields['post_id']).toBeDefined(); diff --git a/packages/core/database/src/__tests__/dialect-extend/dialect-extend.test.ts b/packages/core/database/src/__tests__/dialect-extend/dialect-extend.test.ts new file mode 100644 index 0000000000..e4e95b75d4 --- /dev/null +++ b/packages/core/database/src/__tests__/dialect-extend/dialect-extend.test.ts @@ -0,0 +1,38 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { mockDatabase } from '../'; +import { Database } from '../../database'; +import { BaseDialect } from '../../dialects/base-dialect'; + +describe('dialect extend', () => { + let db: Database; + + beforeEach(async () => { + db = mockDatabase(); + await db.clean({ drop: true }); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should register dialect', async () => { + class SubDialect extends BaseDialect { + static dialectName = 'test'; + + async checkDatabaseVersion(db: Database): Promise { + return true; + } + } + + Database.registerDialect(SubDialect); + expect(Database.getDialect('test')).toBe(SubDialect); + }); +}); diff --git a/packages/core/database/src/__tests__/filter-target-key/single-filter-target-key.test.ts b/packages/core/database/src/__tests__/filter-target-key/single-filter-target-key.test.ts new file mode 100644 index 0000000000..e03e5f88c0 --- /dev/null +++ b/packages/core/database/src/__tests__/filter-target-key/single-filter-target-key.test.ts @@ -0,0 +1,75 @@ +import Database, { mockDatabase } from '@nocobase/database'; + +describe('single filter target key', () => { + let db: Database; + + beforeEach(async () => { + db = mockDatabase(); + await db.clean({ drop: true }); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should update by filter target key', async () => { + const Student = db.collection({ + name: 'students', + autoGenId: false, + filterTargetKey: ['name'], + fields: [ + { + name: 'name', + type: 'string', + primaryKey: true, + }, + { + name: 'age', + type: 'integer', + }, + ], + }); + + await db.sync(); + + const s1 = await Student.repository.create({ + values: { + name: 's1', + age: 10, + }, + }); + + const s2 = await Student.repository.create({ + values: { + name: 's2', + age: 10, + }, + }); + + const s3 = await Student.repository.create({ + values: { + name: 's3', + age: 10, + }, + }); + + await Student.repository.update({ + filter: { + name: 's3', + }, + values: { + age: 20, + }, + }); + + await s3.reload(); + + expect(s3.age).toBe(20); + + const s3Instance = await Student.repository.findOne({ + filterByTk: 's3', + }); + + expect(s3Instance.age).toBe(20); + }); +}); diff --git a/packages/core/database/src/__tests__/inhertits/collection-inherits.test.ts b/packages/core/database/src/__tests__/inhertits/collection-inherits.test.ts index 315cebf059..2a98ef844f 100644 --- a/packages/core/database/src/__tests__/inhertits/collection-inherits.test.ts +++ b/packages/core/database/src/__tests__/inhertits/collection-inherits.test.ts @@ -84,6 +84,35 @@ describe.runIf(isPg())('collection inherits', () => { expect(err).toBeUndefined(); }); + it('should emit afterSync event', async () => { + const Root = db.collection({ + name: 'root', + fields: [ + { name: 'name', type: 'string' }, + { + name: 'bs', + type: 'hasMany', + target: 'b', + foreignKey: 'root_id', + }, + ], + }); + + const Child = db.collection({ + name: 'child', + inherits: ['root'], + }); + + const fn = vi.fn(); + db.on('child.afterSync', (options) => { + fn(); + }); + + await db.sync(); + + expect(fn).toBeCalled(); + }); + it('should append __collection with eager load', async () => { const Root = db.collection({ name: 'root', diff --git a/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts b/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts index e5a0deab12..621c090e40 100644 --- a/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts +++ b/packages/core/database/src/__tests__/interfaces/datetime-interface.test.ts @@ -39,7 +39,7 @@ describe('Date time interface', () => { }, { name: 'dateTime', - type: 'date', + type: 'datetime', uiSchema: { ['x-component-props']: { showTime: true, @@ -74,20 +74,5 @@ describe('Date time interface', () => { expect(await interfaceInstance.toValue(42510)).toBe('2016-05-20T00:00:00.000Z'); expect(await interfaceInstance.toValue('42510')).toBe('2016-05-20T00:00:00.000Z'); expect(await interfaceInstance.toValue('2016-05-20T00:00:00.000Z')).toBe('2016-05-20T00:00:00.000Z'); - expect( - await interfaceInstance.toValue('2016-05-20 04:22:22', { - field: testCollection.getField('dateOnly'), - }), - ).toBe('2016-05-20T00:00:00.000Z'); - expect( - await interfaceInstance.toValue('2016-05-20 01:00:00', { - field: testCollection.getField('dateTime'), - }), - ).toBe(dayjs('2016-05-20 01:00:00').toISOString()); - expect( - await interfaceInstance.toValue('2016-05-20 01:00:00', { - field: testCollection.getField('dateTimeGmt'), - }), - ).toBe('2016-05-20T01:00:00.000Z'); }); }); diff --git a/packages/core/database/src/__tests__/target-key.test.ts b/packages/core/database/src/__tests__/target-key.test.ts index 4c315ef003..ff040d2deb 100644 --- a/packages/core/database/src/__tests__/target-key.test.ts +++ b/packages/core/database/src/__tests__/target-key.test.ts @@ -75,18 +75,23 @@ describe('targetKey', () => { ], }); await db.sync(); + const r1 = db.getRepository('a1'); const r2 = db.getRepository('b1'); + const b1 = await r2.create({ values: {}, }); + await r1.create({ values: { name: 'a1', b1: [b1.toJSON()], }, }); + const b1r = await b1.reload(); + expect(b1r.a1Id).toBe(b1.id); }); diff --git a/packages/core/database/src/__tests__/update-association-values.test.ts b/packages/core/database/src/__tests__/update-association-values.test.ts index d0703c86bc..0948ece271 100644 --- a/packages/core/database/src/__tests__/update-association-values.test.ts +++ b/packages/core/database/src/__tests__/update-association-values.test.ts @@ -21,6 +21,72 @@ describe('update associations', () => { await db.close(); }); + it('should update associations with target key', async () => { + const T1 = db.collection({ + name: 'test1', + autoGenId: false, + timestamps: false, + filterTargetKey: 'id_', + fields: [ + { + name: 'id_', + type: 'string', + }, + { + type: 'hasMany', + name: 't2', + foreignKey: 'nvarchar2', + targetKey: 'varchar_', + sourceKey: 'id_', + target: 'test2', + }, + ], + }); + + const T2 = db.collection({ + name: 'test2', + autoGenId: false, + timestamps: false, + filterTargetKey: 'varchar_', + fields: [ + { + name: 'varchar_', + type: 'string', + unique: true, + }, + { + name: 'nvarchar2', + type: 'string', + }, + ], + }); + + await db.sync(); + + const t2 = await T2.repository.create({ + values: { + varchar_: '1', + }, + }); + + await T1.repository.create({ + values: { + id_: 1, + t2: [ + { + varchar_: '1', + }, + ], + }, + }); + + const t1 = await T1.repository.findOne({ + appends: ['t2'], + }); + + expect(t1['t2'][0]['varchar_']).toBe('1'); + }); + it('hasOne', async () => { db.collection({ name: 'a', diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts index 9cc17f003f..e6ecd56ed2 100644 --- a/packages/core/database/src/collection.ts +++ b/packages/core/database/src/collection.ts @@ -10,6 +10,7 @@ import merge from 'deepmerge'; import { EventEmitter } from 'events'; import { default as _, default as lodash } from 'lodash'; +import safeJsonStringify from 'safe-json-stringify'; import { ModelOptions, ModelStatic, @@ -25,7 +26,6 @@ import { BelongsToField, Field, FieldOptions, HasManyField } from './fields'; import { Model } from './model'; import { Repository } from './repository'; import { checkIdentifier, md5, snakeCase } from './utils'; -import safeJsonStringify from 'safe-json-stringify'; export type RepositoryType = typeof Repository; @@ -126,6 +126,7 @@ export interface CollectionOptions extends Omit */ origin?: string; asStrategyResource?: boolean; + [key: string]: any; } @@ -155,6 +156,7 @@ export class Collection< this.modelInit(); this.db.modelCollection.set(this.model, this); + this.db.modelNameCollectionMap.set(this.model.name, this); // set tableName to collection map // the form of key is `${schema}.${tableName}` if schema exists @@ -173,6 +175,10 @@ export class Collection< const targetKey = this.options?.filterTargetKey; if (Array.isArray(targetKey)) { + if (targetKey.length === 1) { + return targetKey[0]; + } + return targetKey; } @@ -187,10 +193,6 @@ export class Collection< return this.model.primaryKeyAttribute; } - isMultiFilterTargetKey() { - return Array.isArray(this.filterTargetKey) && this.filterTargetKey.length > 1; - } - get name() { return this.options.name; } @@ -223,6 +225,10 @@ export class Collection< } } + isMultiFilterTargetKey() { + return Array.isArray(this.filterTargetKey) && this.filterTargetKey.length > 1; + } + tableName() { const { name, tableName } = this.options; const tName = tableName || name; @@ -259,8 +265,72 @@ export class Collection< M = model; } + const collection = this; + // @ts-ignore this.model = class extends M {}; + + Object.defineProperty(this.model, 'primaryKeyAttribute', { + get: function () { + const singleFilterTargetKey: string = (() => { + if (!collection.options.filterTargetKey) { + return null; + } + + if (Array.isArray(collection.options.filterTargetKey) && collection.options.filterTargetKey.length === 1) { + return collection.options.filterTargetKey[0]; + } + + return collection.options.filterTargetKey as string; + })(); + + if (!this._primaryKeyAttribute && singleFilterTargetKey && collection.getField(singleFilterTargetKey)) { + return singleFilterTargetKey; + } + + return this._primaryKeyAttribute; + }.bind(this.model), + + set(value) { + this._primaryKeyAttribute = value; + }, + }); + + Object.defineProperty(this.model, 'primaryKeyAttributes', { + get: function () { + if (Array.isArray(this._primaryKeyAttributes) && this._primaryKeyAttributes.length) { + return this._primaryKeyAttributes; + } + + if (collection.options.filterTargetKey) { + const fields = lodash.castArray(collection.options.filterTargetKey); + if (fields.every((field) => collection.getField(field))) { + return fields; + } + } + + return this._primaryKeyAttributes; + }.bind(this.model), + + set(value) { + this._primaryKeyAttributes = value; + }, + }); + + Object.defineProperty(this.model, 'primaryKeyField', { + get: function () { + if (this.primaryKeyAttribute) { + return this.rawAttributes[this.primaryKeyAttribute].field || this.primaryKeyAttribute; + } + + return null; + }.bind(this.model), + + set(val) { + this._primaryKeyField = val; + }, + }); + this.model.init(null, this.sequelizeModelOptions()); this.model.options.modelName = this.options.name; @@ -299,6 +369,10 @@ export class Collection< return this.fields.get(name); } + getFieldByField(field: string): Field { + return this.findField((f) => f.options.field === field); + } + getFields() { return [...this.fields.values()]; } @@ -587,9 +661,14 @@ export class Collection< updateOptions(options: CollectionOptions, mergeOptions?: any) { let newOptions = lodash.cloneDeep(options); newOptions = merge(this.options, newOptions, mergeOptions); - this.context.database.emit('beforeUpdateCollection', this, newOptions); - this.options = newOptions; + if (options.filterTargetKey) { + newOptions.filterTargetKey = options.filterTargetKey; + } + + this.context.database.emit('beforeUpdateCollection', this, newOptions); + + this.options = newOptions; this.setFields(options.fields, false); if (options.repository) { this.setRepository(options.repository); @@ -812,6 +891,16 @@ export class Collection< return `${schema}.${tableName}`; } + public getRealTableName(quoted = false) { + const realname = this.tableNameAsString(); + return !quoted ? realname : this.db.sequelize.getQueryInterface().quoteIdentifiers(realname); + } + + public getRealFieldName(name: string, quoted = false) { + const realname = this.model.getAttributes()[name].field; + return !quoted ? name : this.db.sequelize.getQueryInterface().quoteIdentifier(realname); + } + public getTableNameWithSchemaAsString() { const tableName = this.model.tableName; @@ -847,21 +936,20 @@ export class Collection< } unavailableActions() { - if (this.options.template === 'file') { - return ['create', 'update', 'destroy']; - } - return []; } protected sequelizeModelOptions() { const { name } = this.options; - return { + + const attr = { ..._.omit(this.options, ['name', 'fields', 'model', 'targetKey']), modelName: name, sequelize: this.context.database.sequelize, tableName: this.tableName(), }; + + return attr; } protected bindFieldEventListener() { diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts index 3b5117b50c..41ccf770a3 100644 --- a/packages/core/database/src/database.ts +++ b/packages/core/database/src/database.ts @@ -18,7 +18,6 @@ import lodash from 'lodash'; import { nanoid } from 'nanoid'; import { basename, isAbsolute, resolve } from 'path'; import safeJsonStringify from 'safe-json-stringify'; -import semver from 'semver'; import { DataTypes, ModelStatic, @@ -41,7 +40,7 @@ import { referentialIntegrityCheck } from './features/referential-integrity-chec import { ArrayFieldRepository } from './field-repository/array-field-repository'; import * as FieldTypes from './fields'; import { Field, FieldContext, RelationField } from './fields'; -import { checkDatabaseVersion } from './helpers'; +import { checkDatabaseVersion, registerDialects } from './helpers'; import { InheritedCollection } from './inherited-collection'; import InheritanceMap from './inherited-map'; import { InterfaceManager } from './interface-manager'; @@ -85,6 +84,7 @@ import { import { patchSequelizeQueryInterface, snakeCase } from './utils'; import { BaseValueParser, registerFieldValueParsers } from './value-parsers'; import { ViewCollection } from './view-collection'; +import { BaseDialect } from './dialects/base-dialect'; export type MergeOptions = merge.Options; @@ -129,35 +129,9 @@ export type AddMigrationsOptions = { type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any; -export const DialectVersionAccessors = { - sqlite: { - sql: 'select sqlite_version() as version', - get: (v: string) => v, - }, - mysql: { - sql: 'select version() as version', - get: (v: string) => { - const m = /([\d+.]+)/.exec(v); - return m[0]; - }, - }, - mariadb: { - sql: 'select version() as version', - get: (v: string) => { - const m = /([\d+.]+)/.exec(v); - return m[0]; - }, - }, - postgres: { - sql: 'select version() as version', - get: (v: string) => { - const m = /([\d+.]+)/.exec(v); - return semver.minVersion(m[0]).version; - }, - }, -}; - export class Database extends EventEmitter implements AsyncEmitter { + static dialects = new Map(); + sequelize: Sequelize; migrator: Umzug; migrations: Migrations; @@ -168,8 +142,10 @@ export class Database extends EventEmitter implements AsyncEmitter { repositories = new Map(); operators = new Map(); collections = new Map(); + collectionsSort = new Map(); pendingFields = new Map(); modelCollection = new Map, Collection>(); + modelNameCollectionMap = new Map(); tableNameCollectionMap = new Map(); context: any = {}; queryInterface: QueryInterface; @@ -181,13 +157,31 @@ export class Database extends EventEmitter implements AsyncEmitter { delayCollectionExtend = new Map(); logger: Logger; interfaceManager = new InterfaceManager(this); - collectionFactory: CollectionFactory = new CollectionFactory(this); + dialect: BaseDialect; + declare emitAsync: (event: string | symbol, ...args: any[]) => Promise; + static registerDialect(dialect: typeof BaseDialect) { + this.dialects.set(dialect.dialectName, dialect); + } + + static getDialect(name: string) { + return this.dialects.get(name); + } + constructor(options: DatabaseOptions) { super(); + const dialectClass = Database.getDialect(options.dialect); + + if (!dialectClass) { + throw new Error(`unsupported dialect ${options.dialect}`); + } + + // @ts-ignore + this.dialect = new dialectClass(); + const opts = { sync: { alter: { @@ -241,7 +235,12 @@ export class Database extends EventEmitter implements AsyncEmitter { }); } + if (options.logging && process.env['DB_SQL_BENCHMARK'] == 'true') { + opts.benchmark = true; + } + this.options = opts; + this.logger.debug( `create database instance: ${safeJsonStringify( // remove sensitive information @@ -373,21 +372,7 @@ export class Database extends EventEmitter implements AsyncEmitter { * @internal */ sequelizeOptions(options) { - if (options.dialect === 'postgres') { - if (!options.hooks) { - options.hooks = {}; - } - - if (!options.hooks['afterConnect']) { - options.hooks['afterConnect'] = []; - } - - options.hooks['afterConnect'].push(async (connection) => { - await connection.query('SET search_path TO public;'); - }); - } - - return options; + return this.dialect.getSequelizeOptions(options); } /** @@ -527,6 +512,10 @@ export class Database extends EventEmitter implements AsyncEmitter { return this.inDialect('mysql', 'mariadb'); } + isPostgresCompatibleDialect() { + return this.inDialect('postgres'); + } + /** * Add collection to database * @param options @@ -584,6 +573,10 @@ export class Database extends EventEmitter implements AsyncEmitter { return field; } + getCollectionByModelName(name: string) { + return this.modelNameCollectionMap.get(name); + } + /** * get exists collection by its name * @param name @@ -1036,5 +1029,6 @@ export const defineCollection = (collectionOptions: CollectionOptions) => { }; applyMixins(Database, [AsyncEmitter]); +registerDialects(); export default Database; diff --git a/packages/core/database/src/dialects/base-dialect.ts b/packages/core/database/src/dialects/base-dialect.ts new file mode 100644 index 0000000000..c47d7bdf94 --- /dev/null +++ b/packages/core/database/src/dialects/base-dialect.ts @@ -0,0 +1,52 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Database, DatabaseOptions } from '../database'; +import semver from 'semver'; + +export interface DialectVersionGuard { + sql: string; + get: (v: string) => string; + version: string; +} + +export abstract class BaseDialect { + static dialectName: string; + + getSequelizeOptions(options: DatabaseOptions) { + return options; + } + + async checkDatabaseVersion(db: Database): Promise { + const versionGuard = this.getVersionGuard(); + + const result = await db.sequelize.query(versionGuard.sql, { + type: 'SELECT', + }); + + // @ts-ignore + const version = versionGuard.get(result?.[0]?.version); + + const versionResult = semver.satisfies(version, versionGuard.version); + + if (!versionResult) { + throw new Error( + `to use ${(this.constructor as typeof BaseDialect).dialectName}, please ensure the version is ${ + versionGuard.version + }, current version is ${version}`, + ); + } + + return true; + } + + getVersionGuard(): DialectVersionGuard { + throw new Error('not implemented'); + } +} diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/en-US.ts b/packages/core/database/src/dialects/index.ts similarity index 90% rename from packages/plugins/@nocobase/plugin-api-keys/src/server/locale/en-US.ts rename to packages/core/database/src/dialects/index.ts index f3f1abbf93..26a7fef68a 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/en-US.ts +++ b/packages/core/database/src/dialects/index.ts @@ -7,4 +7,4 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -export default {}; +export * from './base-dialect'; diff --git a/packages/core/database/src/dialects/mariadb-dialect.ts b/packages/core/database/src/dialects/mariadb-dialect.ts new file mode 100644 index 0000000000..c7ea225b2e --- /dev/null +++ b/packages/core/database/src/dialects/mariadb-dialect.ts @@ -0,0 +1,25 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { BaseDialect } from './base-dialect'; + +export class MariadbDialect extends BaseDialect { + static dialectName = 'mariadb'; + + getVersionGuard() { + return { + sql: 'select version() as version', + get: (v: string) => { + const m = /([\d+.]+)/.exec(v); + return m[0]; + }, + version: '>=10.9', + }; + } +} diff --git a/packages/core/database/src/dialects/mysql-dialect.ts b/packages/core/database/src/dialects/mysql-dialect.ts new file mode 100644 index 0000000000..f5a6f9ca64 --- /dev/null +++ b/packages/core/database/src/dialects/mysql-dialect.ts @@ -0,0 +1,25 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { BaseDialect } from './base-dialect'; + +export class MysqlDialect extends BaseDialect { + static dialectName = 'mysql'; + + getVersionGuard() { + return { + sql: 'select version() as version', + get: (v: string) => { + const m = /([\d+.]+)/.exec(v); + return m[0]; + }, + version: '>=8.0.17', + }; + } +} diff --git a/packages/core/database/src/dialects/postgres-dialect.ts b/packages/core/database/src/dialects/postgres-dialect.ts new file mode 100644 index 0000000000..6f204f08e5 --- /dev/null +++ b/packages/core/database/src/dialects/postgres-dialect.ts @@ -0,0 +1,42 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import semver from 'semver'; +import { BaseDialect } from './base-dialect'; + +export class PostgresDialect extends BaseDialect { + static dialectName = 'postgres'; + + getSequelizeOptions(options: any) { + if (!options.hooks) { + options.hooks = {}; + } + + if (!options.hooks['afterConnect']) { + options.hooks['afterConnect'] = []; + } + + options.hooks['afterConnect'].push(async (connection) => { + await connection.query('SET search_path TO public;'); + }); + + return options; + } + + getVersionGuard() { + return { + sql: 'select version() as version', + get: (v: string) => { + const m = /([\d+.]+)/.exec(v); + return semver.minVersion(m[0]).version; + }, + version: '>=10', + }; + } +} diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/pt-BR.ts b/packages/core/database/src/dialects/sqlite-dialect.ts similarity index 51% rename from packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/pt-BR.ts rename to packages/core/database/src/dialects/sqlite-dialect.ts index 04a4e13921..2d25a23a28 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/pt-BR.ts +++ b/packages/core/database/src/dialects/sqlite-dialect.ts @@ -7,8 +7,16 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -export default { - "Bulk update": "Atualização em massa", - "After successful bulk update": "Após a atualização em massa bem sucedida", - "Please select the records to be updated": "Por favor, selecione os registros a serem atualizados", -}; +import { BaseDialect } from './base-dialect'; + +export class SqliteDialect extends BaseDialect { + static dialectName = 'sqlite'; + + getVersionGuard() { + return { + sql: 'select sqlite_version() as version', + get: (v: string) => v, + version: '3.x', + }; + } +} diff --git a/packages/core/database/src/features/referential-integrity-check.ts b/packages/core/database/src/features/referential-integrity-check.ts index 6293977367..4593378c90 100644 --- a/packages/core/database/src/features/referential-integrity-check.ts +++ b/packages/core/database/src/features/referential-integrity-check.ts @@ -18,8 +18,7 @@ interface ReferentialIntegrityCheckOptions extends Transactionable { export async function referentialIntegrityCheck(options: ReferentialIntegrityCheckOptions) { const { referencedInstance, db, transaction } = options; - // @ts-ignore - const collection = db.modelCollection.get(referencedInstance.constructor); + const collection = db.getCollectionByModelName(referencedInstance.constructor.name); const collectionName = collection.name; const references = db.referenceMap.getReferences(collectionName); diff --git a/packages/core/database/src/helpers.ts b/packages/core/database/src/helpers.ts index b6787c93a1..4d5f61f07b 100644 --- a/packages/core/database/src/helpers.ts +++ b/packages/core/database/src/helpers.ts @@ -11,7 +11,10 @@ import { Database, IDatabaseOptions } from './database'; import fs from 'fs'; -import semver from 'semver'; +import { MysqlDialect } from './dialects/mysql-dialect'; +import { SqliteDialect } from './dialects/sqlite-dialect'; +import { MariadbDialect } from './dialects/mariadb-dialect'; +import { PostgresDialect } from './dialects/postgres-dialect'; function getEnvValue(key, defaultValue?) { return process.env[key] || defaultValue; @@ -103,55 +106,12 @@ function customLogger(queryString, queryObject) { } } -const dialectVersionAccessors = { - sqlite: { - sql: 'select sqlite_version() as version', - get: (v: string) => v, - version: '3.x', - }, - mysql: { - sql: 'select version() as version', - get: (v: string) => { - const m = /([\d+.]+)/.exec(v); - return m[0]; - }, - version: '>=8.0.17', - }, - mariadb: { - sql: 'select version() as version', - get: (v: string) => { - const m = /([\d+.]+)/.exec(v); - return m[0]; - }, - version: '>=10.9', - }, - postgres: { - sql: 'select version() as version', - get: (v: string) => { - const m = /([\d+.]+)/.exec(v); - return semver.minVersion(m[0]).version; - }, - version: '>=10', - }, -}; - export async function checkDatabaseVersion(db: Database) { - const dialect = db.sequelize.getDialect(); - const accessor = dialectVersionAccessors[dialect]; - if (!accessor) { - throw new Error(`unsupported dialect ${dialect}`); - } - - const result = await db.sequelize.query(accessor.sql, { - type: 'SELECT', - }); - - // @ts-ignore - const version = accessor.get(result?.[0]?.version); - const versionResult = semver.satisfies(version, accessor.version); - if (!versionResult) { - throw new Error(`to use ${dialect}, please ensure the version is ${accessor.version}`); - } - - return true; + await db.dialect.checkDatabaseVersion(db); +} + +export function registerDialects() { + [SqliteDialect, MysqlDialect, MariadbDialect, PostgresDialect].forEach((dialect) => { + Database.registerDialect(dialect); + }); } diff --git a/packages/core/database/src/index.ts b/packages/core/database/src/index.ts index be3a0603b7..3d5e177651 100644 --- a/packages/core/database/src/index.ts +++ b/packages/core/database/src/index.ts @@ -55,3 +55,4 @@ export * from './helpers'; export { default as sqlParser, SQLParserTypes } from './sql-parser'; export * from './interfaces'; export { default as fieldTypeMap } from './view/field-type-map'; +export * from './dialects'; diff --git a/packages/core/database/src/inherited-sync-runner.ts b/packages/core/database/src/inherited-sync-runner.ts index f4971c3ef9..e5060dcb5d 100644 --- a/packages/core/database/src/inherited-sync-runner.ts +++ b/packages/core/database/src/inherited-sync-runner.ts @@ -13,6 +13,7 @@ import lodash from 'lodash'; export class InheritedSyncRunner { static async syncInheritModel(model: any, options: any) { const { transaction } = options; + options.hooks = options.hooks === undefined ? true : !!options.hooks; const inheritedCollection = model.collection as InheritedCollection; const db = inheritedCollection.context.database; @@ -62,10 +63,10 @@ export class InheritedSyncRunner { for (const parent of parents) { const sequenceNameResult = await queryInterface.sequelize.query( `SELECT column_default - FROM information_schema.columns - WHERE table_name = '${parent.model.tableName}' - and table_schema = '${parent.collectionSchema()}' - and "column_name" = 'id';`, + FROM information_schema.columns + WHERE table_name = '${parent.model.tableName}' + and table_schema = '${parent.collectionSchema()}' + and "column_name" = 'id';`, { transaction, }, @@ -87,7 +88,7 @@ export class InheritedSyncRunner { const sequenceName = match[1]; const sequenceCurrentValResult = await queryInterface.sequelize.query( `select last_value - from ${sequenceName}`, + from ${sequenceName}`, { transaction, }, @@ -117,10 +118,10 @@ export class InheritedSyncRunner { const schemaName = sequenceTable.schema; const idColumnSql = `SELECT column_name - FROM information_schema.columns - WHERE table_name = '${tableName}' - and column_name = 'id' - and table_schema = '${schemaName}'; + FROM information_schema.columns + WHERE table_name = '${tableName}' + and column_name = 'id' + and table_schema = '${schemaName}'; `; const idColumnQuery = await queryInterface.sequelize.query(idColumnSql, { @@ -133,7 +134,7 @@ export class InheritedSyncRunner { await queryInterface.sequelize.query( `alter table ${db.utils.quoteTable(sequenceTable)} - alter column id set default nextval('${maxSequenceName}')`, + alter column id set default nextval('${maxSequenceName}')`, { transaction, }, @@ -153,6 +154,14 @@ export class InheritedSyncRunner { } } } + + if (options.hooks) { + await model.runHooks('afterSync', { + ...options, + modelName: model.name, + transaction, + }); + } } static async createTable(tableName, attributes, options, model, parents) { diff --git a/packages/core/database/src/interfaces/boolean-interface.ts b/packages/core/database/src/interfaces/boolean-interface.ts index b263a3641f..583186fd4a 100644 --- a/packages/core/database/src/interfaces/boolean-interface.ts +++ b/packages/core/database/src/interfaces/boolean-interface.ts @@ -40,7 +40,12 @@ export class BooleanInterface extends BaseInterface { const option = enumConfig.find((item) => item.value === value); return option?.label; } else { - return value ? '是' : value === null || value === undefined ? '' : '否'; + const label = value ? 'True' : value === null || value === undefined ? '' : 'False'; + if (ctx?.t) { + return ctx.t(label, { ns: 'action-export' }); + } + + return label; } } } diff --git a/packages/core/database/src/interfaces/date-interface.ts b/packages/core/database/src/interfaces/date-interface.ts new file mode 100644 index 0000000000..6b8aedcdb6 --- /dev/null +++ b/packages/core/database/src/interfaces/date-interface.ts @@ -0,0 +1,7 @@ +import { DatetimeInterface } from './datetime-interface'; + +export class DateInterface extends DatetimeInterface { + toString(value: any, ctx?: any): any { + return value; + } +} diff --git a/packages/core/database/src/interfaces/datetime-interface.ts b/packages/core/database/src/interfaces/datetime-interface.ts index fed3a38753..85661d5933 100644 --- a/packages/core/database/src/interfaces/datetime-interface.ts +++ b/packages/core/database/src/interfaces/datetime-interface.ts @@ -8,7 +8,7 @@ */ import { BaseInterface } from './base-interface'; -import { getDefaultFormat, moment2str, str2moment } from '@nocobase/utils'; +import { getDefaultFormat, str2moment } from '@nocobase/utils'; import dayjs from 'dayjs'; import { getJsDateFromExcel } from 'excel-date-to-js'; @@ -51,11 +51,7 @@ export class DatetimeInterface extends BaseInterface { } else if (isNumeric(value)) { return getJsDateFromExcel(value).toISOString(); } else if (typeof value === 'string') { - const props = ctx.field?.options?.uiSchema?.['x-component-props'] || {}; - const m = dayjs(value); - if (m.isValid()) { - return moment2str(m, props); - } + return value; } throw new Error(`Invalid date - ${value}`); diff --git a/packages/core/database/src/interfaces/datetime-no-tz-interface.ts b/packages/core/database/src/interfaces/datetime-no-tz-interface.ts new file mode 100644 index 0000000000..da585653e1 --- /dev/null +++ b/packages/core/database/src/interfaces/datetime-no-tz-interface.ts @@ -0,0 +1,49 @@ +import { DatetimeInterface } from './datetime-interface'; +import dayjs from 'dayjs'; +import { getJsDateFromExcel } from 'excel-date-to-js'; +import { getDefaultFormat, str2moment } from '@nocobase/utils'; + +function isDate(v) { + return v instanceof Date; +} + +function isNumeric(str: any) { + if (typeof str === 'number') return true; + if (typeof str != 'string') return false; + return !isNaN(str as any) && !isNaN(parseFloat(str)); +} + +export class DatetimeNoTzInterface extends DatetimeInterface { + async toValue(value: any, ctx: any = {}): Promise { + if (!value) { + return null; + } + + if (typeof value === 'string') { + const match = /^(\d{4})[-/]?(\d{2})[-/]?(\d{2})$/.exec(value); + if (match) { + return `${match[1]}-${match[2]}-${match[3]}`; + } + } + + if (dayjs.isDayjs(value)) { + return value; + } else if (isDate(value)) { + return value; + } else if (isNumeric(value)) { + const date = getJsDateFromExcel(value); + return date.toISOString(); + } else if (typeof value === 'string') { + return value; + } + + throw new Error(`Invalid date - ${value}`); + } + + toString(value: any, ctx?: any) { + const props = this.options?.uiSchema?.['x-component-props'] ?? {}; + const format = getDefaultFormat(props); + const m = str2moment(value, { ...props }); + return m ? m.format(format) : ''; + } +} diff --git a/packages/core/database/src/interfaces/index.ts b/packages/core/database/src/interfaces/index.ts index 00eba5d9d8..038ada52cc 100644 --- a/packages/core/database/src/interfaces/index.ts +++ b/packages/core/database/src/interfaces/index.ts @@ -12,4 +12,6 @@ export * from './percent-interface'; export * from './multiple-select-interface'; export * from './select-interface'; export * from './datetime-interface'; +export * from './datetime-no-tz-interface'; export * from './boolean-interface'; +export * from './date-interface'; diff --git a/packages/core/database/src/interfaces/multiple-select-interface.ts b/packages/core/database/src/interfaces/multiple-select-interface.ts index 66548c3e35..3f3203dda5 100644 --- a/packages/core/database/src/interfaces/multiple-select-interface.ts +++ b/packages/core/database/src/interfaces/multiple-select-interface.ts @@ -36,7 +36,16 @@ export class MultipleSelectInterface extends BaseInterface { .castArray(value) .map((value) => { const option = enumConfig.find((item) => item.value === value); - return option ? option.label : value; + + if (option) { + if (ctx?.t) { + return ctx.t(option.label, { ns: 'lm-collections' }); + } + + return option.label; + } + + return value; }) .join(','); } diff --git a/packages/core/database/src/interfaces/select-interface.ts b/packages/core/database/src/interfaces/select-interface.ts index e05f283f49..cd12a0fc5b 100644 --- a/packages/core/database/src/interfaces/select-interface.ts +++ b/packages/core/database/src/interfaces/select-interface.ts @@ -32,6 +32,15 @@ export class SelectInterface extends BaseInterface { toString(value: any, ctx?: any) { const enumConfig = this.options.uiSchema?.enum || []; const option = enumConfig.find((item) => item.value === value); - return option?.label || value; + + if (option) { + if (ctx?.t) { + return ctx.t(option.label, { ns: 'lm-collections' }); + } + + return option.label; + } + + return value; } } diff --git a/packages/core/database/src/interfaces/to-many-interface.ts b/packages/core/database/src/interfaces/to-many-interface.ts index 664ff692f7..8b6aef65ad 100644 --- a/packages/core/database/src/interfaces/to-many-interface.ts +++ b/packages/core/database/src/interfaces/to-many-interface.ts @@ -15,6 +15,8 @@ export class ToManyInterface extends BaseInterface { return null; } + str = `${str}`.trim(); + const items = str.split(','); const { filterKey, targetCollection, transaction } = ctx; @@ -28,7 +30,7 @@ export class ToManyInterface extends BaseInterface { // check if all items are found items.forEach((item) => { - if (!targetInstances.find((targetInstance) => targetInstance[filterKey] === item)) { + if (!targetInstances.find((targetInstance) => targetInstance[filterKey] == item)) { throw new Error(`"${item}" not found in ${targetCollection.model.name} ${filterKey}`); } }); diff --git a/packages/core/database/src/interfaces/to-one-interface.ts b/packages/core/database/src/interfaces/to-one-interface.ts index e2e58b6c8e..568cc6281a 100644 --- a/packages/core/database/src/interfaces/to-one-interface.ts +++ b/packages/core/database/src/interfaces/to-one-interface.ts @@ -19,7 +19,7 @@ export class ToOneInterface extends BaseInterface { return null; } - const { filterKey, targetCollection, transaction } = ctx; + const { filterKey, associationField, targetCollection, transaction } = ctx; const targetInstance = await targetCollection.repository.findOne({ filter: { @@ -31,8 +31,9 @@ export class ToOneInterface extends BaseInterface { if (!targetInstance) { throw new Error(`"${str}" not found in ${targetCollection.model.name} ${filterKey}`); } - const primaryKeyAttribute = targetCollection.model.primaryKeyAttribute; - return targetInstance[primaryKeyAttribute]; + const targetKey = associationField.targetKey || targetCollection.model.primaryKeyAttribute; + + return targetInstance[targetKey]; } } diff --git a/packages/core/database/src/interfaces/utils.ts b/packages/core/database/src/interfaces/utils.ts index e23ac2e49f..170687f6e5 100644 --- a/packages/core/database/src/interfaces/utils.ts +++ b/packages/core/database/src/interfaces/utils.ts @@ -10,7 +10,9 @@ import Database from '../database'; import { BooleanInterface, + DateInterface, DatetimeInterface, + DatetimeNoTzInterface, MultipleSelectInterface, PercentInterface, SelectInterface, @@ -36,6 +38,9 @@ const interfaces = { radioGroup: SelectInterface, percent: PercentInterface, datetime: DatetimeInterface, + datetimeNoTz: DatetimeNoTzInterface, + unixTimestamp: DatetimeInterface, + date: DateInterface, createdAt: DatetimeInterface, updatedAt: DatetimeInterface, boolean: BooleanInterface, diff --git a/packages/core/database/src/operators/date.ts b/packages/core/database/src/operators/date.ts index 9c0157e61c..24b218a90a 100644 --- a/packages/core/database/src/operators/date.ts +++ b/packages/core/database/src/operators/date.ts @@ -8,7 +8,7 @@ */ import { parseDate } from '@nocobase/utils'; -import { Op } from 'sequelize'; +import { Op, Sequelize } from 'sequelize'; import moment from 'moment'; function isDate(input) { @@ -17,7 +17,7 @@ function isDate(input) { const toDate = (date, options: any = {}) => { const { ctx } = options; - const val = isDate(date) ? date : new Date(date); + let val = isDate(date) ? date : new Date(date); const field = ctx.db.getFieldByPath(ctx.fieldPath); if (!field) { @@ -25,18 +25,25 @@ const toDate = (date, options: any = {}) => { } if (field.constructor.name === 'UnixTimestampField') { - return field.dateToValue(val); + val = field.dateToValue(val); } if (field.constructor.name === 'DatetimeNoTzField') { - return moment(val).utcOffset('+00:00').format('YYYY-MM-DD HH:mm:ss'); + val = moment(val).utcOffset('+00:00').format('YYYY-MM-DD HH:mm:ss'); } if (field.constructor.name === 'DateOnlyField') { - return moment(val).format('YYYY-MM-DD HH:mm:ss'); + val = moment(val).format('YYYY-MM-DD HH:mm:ss'); } - return val; + const eventObj = { + val, + fieldType: field.type, + }; + + ctx.db.emit('filterToDate', eventObj); + + return eventObj.val; }; function parseDateTimezone(ctx) { @@ -57,10 +64,6 @@ function parseDateTimezone(ctx) { return ctx.db.options.timezone; } -function isDatetimeString(str) { - return /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(str); -} - export default { $dateOn(value, ctx) { const r = parseDate(value, { diff --git a/packages/core/database/src/operators/string.ts b/packages/core/database/src/operators/string.ts index be46cd1b47..24d87ec601 100644 --- a/packages/core/database/src/operators/string.ts +++ b/packages/core/database/src/operators/string.ts @@ -16,6 +16,11 @@ function escapeLike(value: string) { export default { $includes(value, ctx) { + if (value === null) { + return { + [Op.is]: null, + }; + } if (Array.isArray(value)) { const conditions = value.map((item) => ({ [isPg(ctx) ? Op.iLike : Op.like]: `%${escapeLike(item)}%`, @@ -32,6 +37,11 @@ export default { }, $notIncludes(value, ctx) { + if (value === null) { + return { + [Op.not]: null, + }; + } if (Array.isArray(value)) { const conditions = value.map((item) => ({ [isPg(ctx) ? Op.notILike : Op.notLike]: `%${escapeLike(item)}%`, diff --git a/packages/core/database/src/options-parser.ts b/packages/core/database/src/options-parser.ts index 806d69074f..d7acc8e103 100644 --- a/packages/core/database/src/options-parser.ts +++ b/packages/core/database/src/options-parser.ts @@ -182,7 +182,12 @@ export class OptionsParser { sortField.push(direction); if (this.database.isMySQLCompatibleDialect()) { - orderParams.push([Sequelize.fn('ISNULL', Sequelize.col(`${this.model.name}.${sortField[0]}`))]); + const fieldName = sortField[0]; + + // @ts-ignore + if (this.model.fieldRawAttributesMap[fieldName]) { + orderParams.push([Sequelize.fn('ISNULL', Sequelize.col(`${this.model.name}.${sortField[0]}`))]); + } } orderParams.push(sortField); } diff --git a/packages/core/database/src/query-interface/query-interface-builder.ts b/packages/core/database/src/query-interface/query-interface-builder.ts index 2bed6cc9f3..4dda94f4eb 100644 --- a/packages/core/database/src/query-interface/query-interface-builder.ts +++ b/packages/core/database/src/query-interface/query-interface-builder.ts @@ -20,7 +20,12 @@ export default function buildQueryInterface(db: Database) { sqlite: SqliteQueryInterface, }; + if (db.isPostgresCompatibleDialect()) { + return new PostgresQueryInterface(db); + } + const dialect = db.options.dialect; + if (!map[dialect]) { return null; } diff --git a/packages/core/database/src/update-associations.ts b/packages/core/database/src/update-associations.ts index 655a1fadbd..8bc07fb751 100644 --- a/packages/core/database/src/update-associations.ts +++ b/packages/core/database/src/update-associations.ts @@ -18,10 +18,10 @@ import { ModelStatic, Transactionable, } from 'sequelize'; -import Database from './database'; import { Model } from './model'; import { UpdateGuard } from './update-guard'; import { TargetKey } from './repository'; +import Database from './database'; function isUndefinedOrNull(value: any) { return typeof value === 'undefined' || value === null; @@ -449,7 +449,8 @@ export async function updateMultipleAssociation( } else if (item.sequelize) { setItems.push(item); } else if (typeof item === 'object') { - const targetKey = (association as any).targetKey || 'id'; + // @ts-ignore + const targetKey = (association as any).targetKey || association.options.targetKey || 'id'; if (item[targetKey]) { const attributes = { @@ -468,16 +469,19 @@ export async function updateMultipleAssociation( await model[setAccessor](setItems, { transaction, context, individualHooks: true }); const newItems = []; + const pk = association.target.primaryKeyAttribute; - const tmpKey = association['options']?.['targetKey']; let targetKey = pk; const db = model.constructor['database'] as Database; + + const tmpKey = association['options']?.['targetKey']; if (tmpKey !== pk) { const targetKeyFieldOptions = db.getFieldByPath(`${association.target.name}.${tmpKey}`)?.options; if (targetKeyFieldOptions?.unique) { targetKey = tmpKey; } } + for (const item of objectItems) { const through = (association).through ? (association).through.model.name : null; @@ -550,7 +554,10 @@ export async function updateMultipleAssociation( } for (const newItem of newItems) { - const existIndexInSetItems = setItems.findIndex((setItem) => setItem[targetKey] === newItem[targetKey]); + // @ts-ignore + const findTargetKey = (association as any).targetKey || association.options.targetKey || targetKey; + + const existIndexInSetItems = setItems.findIndex((setItem) => setItem[findTargetKey] === newItem[findTargetKey]); if (existIndexInSetItems !== -1) { setItems[existIndexInSetItems] = newItem; diff --git a/packages/core/devtools/README.md b/packages/core/devtools/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/devtools/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/devtools/package.json b/packages/core/devtools/package.json index 961a905fbc..fc48c11f45 100644 --- a/packages/core/devtools/package.json +++ b/packages/core/devtools/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/devtools", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "license": "AGPL-3.0", "main": "./src/index.js", "dependencies": { - "@nocobase/build": "1.4.0-alpha", - "@nocobase/client": "1.4.0-alpha", - "@nocobase/test": "1.4.0-alpha", + "@nocobase/build": "1.4.0-alpha.11", + "@nocobase/client": "1.4.0-alpha.11", + "@nocobase/test": "1.4.0-alpha.11", "@types/koa": "^2.13.4", "@types/koa-bodyparser": "^4.3.4", "@types/lodash": "^4.14.177", diff --git a/packages/core/devtools/umiConfig.js b/packages/core/devtools/umiConfig.js index ff56d142fb..f0a4f5fd2b 100644 --- a/packages/core/devtools/umiConfig.js +++ b/packages/core/devtools/umiConfig.js @@ -8,7 +8,7 @@ const path = require('path'); console.log('VERSION: ', packageJson.version); function getUmiConfig() { - const { APP_PORT, API_BASE_URL, API_CLIENT_STORAGE_PREFIX, APP_PUBLIC_PATH } = process.env; + const { APP_PORT, API_BASE_URL, API_CLIENT_STORAGE_TYPE, API_CLIENT_STORAGE_PREFIX, APP_PUBLIC_PATH } = process.env; const API_BASE_PATH = process.env.API_BASE_PATH || '/api/'; const PROXY_TARGET_URL = process.env.PROXY_TARGET_URL || `http://127.0.0.1:${APP_PORT}`; const LOCAL_STORAGE_BASE_URL = 'storage/uploads/'; @@ -41,6 +41,7 @@ function getUmiConfig() { 'process.env.WS_PATH': process.env.WS_PATH, 'process.env.API_BASE_URL': API_BASE_URL || API_BASE_PATH, 'process.env.API_CLIENT_STORAGE_PREFIX': API_CLIENT_STORAGE_PREFIX, + 'process.env.API_CLIENT_STORAGE_TYPE': API_CLIENT_STORAGE_TYPE, 'process.env.APP_ENV': process.env.APP_ENV, 'process.env.VERSION': packageJson.version, 'process.env.WEBSOCKET_URL': process.env.WEBSOCKET_URL, @@ -53,6 +54,15 @@ function getUmiConfig() { target: PROXY_TARGET_URL, changeOrigin: true, pathRewrite: { [`^${API_BASE_PATH}`]: API_BASE_PATH }, + onProxyRes(proxyRes, req, res) { + if (req.headers.accept === 'text/event-stream') { + res.writeHead(res.statusCode, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-transform', + Connection: 'keep-alive', + }); + } + }, }, // for local storage ...getLocalStorageProxy(), diff --git a/packages/core/evaluators/README.md b/packages/core/evaluators/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/evaluators/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/evaluators/package.json b/packages/core/evaluators/package.json index fdaf6ac29f..08dde6d3c3 100644 --- a/packages/core/evaluators/package.json +++ b/packages/core/evaluators/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/evaluators", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "main": "./lib/index.js", "types": "./lib/index.d.ts", "license": "AGPL-3.0", "dependencies": { "@formulajs/formulajs": "4.2.0", - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/utils": "1.4.0-alpha.11", "mathjs": "^10.6.0" }, "repository": { diff --git a/packages/core/evaluators/src/client/index.tsx b/packages/core/evaluators/src/client/index.tsx index 6079894ebf..b2902807bb 100644 --- a/packages/core/evaluators/src/client/index.tsx +++ b/packages/core/evaluators/src/client/index.tsx @@ -22,8 +22,8 @@ export interface Evaluator { export const evaluators = new Registry(); -evaluators.register('math.js', mathjs); evaluators.register('formula.js', formulajs); +evaluators.register('math.js', mathjs); evaluators.register('string', string); export function getOptions() { diff --git a/packages/core/lock-manager/package.json b/packages/core/lock-manager/package.json index b3296dd5b0..e4781e6bed 100644 --- a/packages/core/lock-manager/package.json +++ b/packages/core/lock-manager/package.json @@ -1,12 +1,12 @@ { "name": "@nocobase/lock-manager", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "lib/index.js", "license": "AGPL-3.0", "dependencies": { }, "devDependencies": { - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/utils": "1.4.0-alpha.11", "async-mutex": "^0.5.0" } } diff --git a/packages/core/logger/README.md b/packages/core/logger/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/logger/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/logger/package.json b/packages/core/logger/package.json index 86417b9675..775b4b479b 100644 --- a/packages/core/logger/package.json +++ b/packages/core/logger/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/logger", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "nocobase logging library", "license": "AGPL-3.0", "main": "./lib/index.js", diff --git a/packages/core/logger/src/request-logger.ts b/packages/core/logger/src/request-logger.ts index fc6e78a401..81a07ec3ac 100644 --- a/packages/core/logger/src/request-logger.ts +++ b/packages/core/logger/src/request-logger.ts @@ -34,7 +34,7 @@ export interface RequestLoggerOptions extends LoggerOptions { } export const requestLogger = (appName: string, requestLogger: Logger, options?: RequestLoggerOptions) => { - return async (ctx, next) => { + return async function requestLoggerMiddleware(ctx, next) { const reqId = ctx.reqId; const path = /^\/api\/(.+):(.+)/.exec(ctx.path); const contextLogger = ctx.app.log.child({ reqId, module: path?.[1], submodule: path?.[2] }); @@ -71,6 +71,7 @@ export const requestLogger = (appName: string, requestLogger: Logger, options?: cost, app: appName, reqId, + bodySize: ctx.response.length, }; if (Math.floor(status / 100) == 5) { requestLogger.error({ ...info, res: ctx.body?.['errors'] || ctx.body }); diff --git a/packages/core/resourcer/README.md b/packages/core/resourcer/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/resourcer/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/resourcer/package.json b/packages/core/resourcer/package.json index 7c477bc938..0cb1411472 100644 --- a/packages/core/resourcer/package.json +++ b/packages/core/resourcer/package.json @@ -1,12 +1,12 @@ { "name": "@nocobase/resourcer", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "", "main": "./lib/index.js", "types": "./lib/index.d.ts", "license": "AGPL-3.0", "dependencies": { - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/utils": "1.4.0-alpha.11", "deepmerge": "^4.2.2", "koa-compose": "^4.1.0", "lodash": "^4.17.21", diff --git a/packages/core/resourcer/src/action.ts b/packages/core/resourcer/src/action.ts index f2a91affde..34d6b61ad9 100644 --- a/packages/core/resourcer/src/action.ts +++ b/packages/core/resourcer/src/action.ts @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { assign, MergeStrategies, requireModule } from '@nocobase/utils'; +import { assign, MergeStrategies, requireModule, wrapMiddlewareWithLogging } from '@nocobase/utils'; import compose from 'koa-compose'; import _ from 'lodash'; import Middleware, { MiddlewareType } from './middleware'; @@ -375,9 +375,7 @@ export class Action { this.getHandler(), ].filter(Boolean); - // handlers = handlers.map((handler) => prePerfHooksWrap(handler)); - - return handlers; + return handlers.map((fn) => wrapMiddlewareWithLogging(fn)); } /** diff --git a/packages/core/resourcer/src/resourcer.ts b/packages/core/resourcer/src/resourcer.ts index f8609e6c80..b5779ba48b 100644 --- a/packages/core/resourcer/src/resourcer.ts +++ b/packages/core/resourcer/src/resourcer.ts @@ -306,7 +306,9 @@ export class ResourceManager { } middleware({ prefix, accessors, skipIfDataSourceExists = false }: KoaMiddlewareOptions = {}) { - return async (ctx: ResourcerContext, next: () => Promise) => { + const self = this; + + return async function resourcerMiddleware(ctx: ResourcerContext, next: () => Promise) { if (skipIfDataSourceExists) { const dataSource = ctx.get('x-data-source'); if (dataSource) { @@ -314,7 +316,7 @@ export class ResourceManager { } } - ctx.resourcer = this; + ctx.resourcer = self; let params = parseRequest( { @@ -322,8 +324,8 @@ export class ResourceManager { method: ctx.request.method, }, { - prefix: this.options.prefix || prefix, - accessors: this.options.accessors || accessors, + prefix: self.options.prefix || prefix, + accessors: self.options.accessors || accessors, }, ); @@ -332,7 +334,7 @@ export class ResourceManager { } try { - const resource = this.getResource(getNameByParams(params)); + const resource = self.getResource(getNameByParams(params)); // 为关系资源时,暂时需要再执行一遍 parseRequest if (resource.options.type && resource.options.type !== 'single') { @@ -343,8 +345,8 @@ export class ResourceManager { type: resource.options.type, }, { - prefix: this.options.prefix || prefix, - accessors: this.options.accessors || accessors, + prefix: self.options.prefix || prefix, + accessors: self.options.accessors || accessors, }, ); @@ -354,7 +356,7 @@ export class ResourceManager { } // action 需要 clone 之后再赋给 ctx - ctx.action = this.getAction(getNameByParams(params), params.actionName).clone(); + ctx.action = self.getAction(getNameByParams(params), params.actionName).clone(); ctx.action.setContext(ctx); ctx.action.actionName = params.actionName; diff --git a/packages/core/sdk/README.md b/packages/core/sdk/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/sdk/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/sdk/package.json b/packages/core/sdk/package.json index e88a5c0f8e..224b68d70c 100644 --- a/packages/core/sdk/package.json +++ b/packages/core/sdk/package.json @@ -1,11 +1,11 @@ { "name": "@nocobase/sdk", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "lib/index.js", "types": "lib/index.d.ts", "dependencies": { - "axios": "^0.26.1", + "axios": "^1.7.0", "qs": "^6.10.1" }, "devDependencies": { diff --git a/packages/core/sdk/src/APIClient.ts b/packages/core/sdk/src/APIClient.ts index 1b0bbcc0eb..e475c145b8 100644 --- a/packages/core/sdk/src/APIClient.ts +++ b/packages/core/sdk/src/APIClient.ts @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import axios, { AxiosInstance, AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse } from 'axios'; +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, RawAxiosRequestHeaders } from 'axios'; import qs from 'qs'; export interface ActionParams { @@ -267,6 +267,7 @@ export class MemoryStorage extends Storage { interface ExtendedOptions { authClass?: any; + storageType?: 'localStorage' | 'sessionStorage' | 'memory'; storageClass?: any; storagePrefix?: string; } @@ -274,6 +275,7 @@ interface ExtendedOptions { export type APIClientOptions = AxiosInstance | (AxiosRequestConfig & ExtendedOptions); export class APIClient { + options?: APIClientOptions; axios: AxiosInstance; auth: Auth; storage: Storage; @@ -296,14 +298,15 @@ export class APIClient { return headers; } - constructor(instance?: APIClientOptions) { - if (typeof instance === 'function') { - this.axios = instance; + constructor(options?: APIClientOptions) { + this.options = options; + if (typeof options === 'function') { + this.axios = options; } else { - const { authClass, storageClass, storagePrefix = 'NOCOBASE_', ...others } = instance || {}; + const { authClass, storageType, storageClass, storagePrefix = 'NOCOBASE_', ...others } = options || {}; this.storagePrefix = storagePrefix; this.axios = axios.create(others); - this.initStorage(storageClass); + this.initStorage(storageClass, storageType); if (authClass) { this.auth = new authClass(this); } @@ -317,14 +320,20 @@ export class APIClient { this.interceptors(); } - private initStorage(storage?: any) { + private initStorage(storage?: any, storageType = 'localStorage') { if (storage) { this.storage = new storage(this); - } else if (typeof localStorage !== 'undefined') { - this.storage = localStorage; - } else { - this.storage = new MemoryStorage(); + return; } + if (storageType === 'localStorage' && typeof localStorage !== 'undefined') { + this.storage = localStorage; + return; + } + if (storageType === 'sessionStorage' && typeof sessionStorage !== 'undefined') { + this.storage = sessionStorage; + return; + } + this.storage = new MemoryStorage(); } interceptors() { @@ -347,7 +356,7 @@ export class APIClient { return this.axios.request(config); } - resource(name: string, of?: any, headers?: AxiosRequestHeaders, cancel?: boolean): IResource { + resource(name: string, of?: any, headers?: RawAxiosRequestHeaders, cancel?: boolean): IResource { const target = {}; const handler = { get: (_: any, actionName: string) => { @@ -356,7 +365,7 @@ export class APIClient { } let url = name.split('.').join(`/${encodeURIComponent(of) || '_'}/`); - url += `:${actionName}`; + url += `:${actionName.toString()}`; const config: AxiosRequestConfig = { url }; if (['get', 'list'].includes(actionName)) { config['method'] = 'get'; diff --git a/packages/core/server/README.md b/packages/core/server/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/server/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/server/package.json b/packages/core/server/package.json index 36a84bbd43..91a730b2d9 100644 --- a/packages/core/server/package.json +++ b/packages/core/server/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/server", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "lib/index.js", "types": "./lib/index.d.ts", "license": "AGPL-3.0", @@ -10,25 +10,25 @@ "@koa/cors": "^3.1.0", "@koa/multer": "^3.0.2", "@koa/router": "^9.4.0", - "@nocobase/acl": "1.4.0-alpha", - "@nocobase/actions": "1.4.0-alpha", - "@nocobase/auth": "1.4.0-alpha", - "@nocobase/cache": "1.4.0-alpha", - "@nocobase/data-source-manager": "1.4.0-alpha", - "@nocobase/database": "1.4.0-alpha", - "@nocobase/evaluators": "1.4.0-alpha", - "@nocobase/lock-manager": "1.4.0-alpha", - "@nocobase/logger": "1.4.0-alpha", - "@nocobase/resourcer": "1.4.0-alpha", - "@nocobase/sdk": "1.4.0-alpha", - "@nocobase/telemetry": "1.4.0-alpha", - "@nocobase/utils": "1.4.0-alpha", - "@types/decompress": "4.2.4", + "@nocobase/acl": "1.4.0-alpha.11", + "@nocobase/actions": "1.4.0-alpha.11", + "@nocobase/auth": "1.4.0-alpha.11", + "@nocobase/cache": "1.4.0-alpha.11", + "@nocobase/data-source-manager": "1.4.0-alpha.11", + "@nocobase/database": "1.4.0-alpha.11", + "@nocobase/evaluators": "1.4.0-alpha.11", + "@nocobase/lock-manager": "1.4.0-alpha.11", + "@nocobase/logger": "1.4.0-alpha.11", + "@nocobase/resourcer": "1.4.0-alpha.11", + "@nocobase/sdk": "1.4.0-alpha.11", + "@nocobase/telemetry": "1.4.0-alpha.11", + "@nocobase/utils": "1.4.0-alpha.11", + "@types/decompress": "4.2.7", "@types/ini": "^1.3.31", "@types/koa-send": "^4.1.3", "@types/multer": "^1.4.5", "async-mutex": "^0.5.0", - "axios": "^0.26.1", + "axios": "^1.7.0", "chalk": "^4.1.1", "commander": "^9.2.0", "cron": "^2.4.4", diff --git a/packages/core/server/src/application.ts b/packages/core/server/src/application.ts index a2f0f498a0..8688826f63 100644 --- a/packages/core/server/src/application.ts +++ b/packages/core/server/src/application.ts @@ -24,8 +24,17 @@ import { } from '@nocobase/logger'; import { ResourceOptions, Resourcer } from '@nocobase/resourcer'; import { Telemetry, TelemetryOptions } from '@nocobase/telemetry'; -import { applyMixins, AsyncEmitter, importModule, Toposort, ToposortOptions } from '@nocobase/utils'; + +import { + applyMixins, + AsyncEmitter, + importModule, + Toposort, + ToposortOptions, + wrapMiddlewareWithLogging, +} from '@nocobase/utils'; import { LockManager, LockManagerOptions } from '@nocobase/lock-manager'; + import { Command, CommandOptions, ParseOptions } from 'commander'; import { randomUUID } from 'crypto'; import glob from 'glob'; @@ -231,7 +240,6 @@ export class Application exten private _maintainingCommandStatus: MaintainingCommandStatus; private _maintainingStatusBeforeCommand: MaintainingCommandStatus | null; private _actionCommand: Command; - private sqlLogger: Logger; public lockManager: LockManager; constructor(public options: ApplicationOptions) { @@ -245,6 +253,24 @@ export class Application exten } } + private static staticCommands = []; + + static addCommand(callback: (app: Application) => void) { + this.staticCommands.push(callback); + } + + private _sqlLogger: Logger; + + get sqlLogger() { + return this._sqlLogger; + } + + protected _logger: SystemLogger; + + get logger() { + return this._logger; + } + protected _started: Date | null = null; /** @@ -254,12 +280,6 @@ export class Application exten return this._started; } - protected _logger: SystemLogger; - - get logger() { - return this._logger; - } - get log() { return this._logger; } @@ -442,6 +462,10 @@ export class Application exten return packageJson.version; } + getPackageVersion() { + return packageJson.version; + } + /** * This method is deprecated and should not be used. * Use {@link #this.pm.addPreset()} instead. @@ -457,7 +481,7 @@ export class Application exten middleware: Koa.Middleware, options?: ToposortOptions, ) { - this.middleware.add(middleware, options); + this.middleware.add(wrapMiddlewareWithLogging(middleware, this.logger), options); return this; } @@ -546,6 +570,11 @@ export class Application exten this._loaded = false; } + async createCacheManager() { + this._cacheManager = await createCacheManager(this, this.options.cacheManager); + return this._cacheManager; + } + async load(options?: LoadOptions) { if (this._loaded) { return; @@ -572,7 +601,11 @@ export class Application exten } } - this._cacheManager = await createCacheManager(this, this.options.cacheManager); + if (this.cacheManager) { + await this.cacheManager.close(); + } + + this._cacheManager = await this.createCacheManager(); this.log.debug('init plugins'); this.setMaintainingMessage('init plugins'); @@ -588,10 +621,12 @@ export class Application exten // Telemetry is initialized after beforeLoad hook // since some configuration may be registered in beforeLoad hook - this.telemetry.init(); - if (this.options.telemetry?.enabled) { - // Start collecting telemetry data if enabled - this.telemetry.start(); + if (!this.telemetry.started) { + this.telemetry.init(); + if (this.options.telemetry?.enabled) { + // Start collecting telemetry data if enabled + this.telemetry.start(); + } } await this.pm.load(options); @@ -921,7 +956,7 @@ export class Application exten await this.reInit(); await this.db.sync(); await this.load({ hooks: false }); - + this._loaded = false; this.log.debug('emit beforeInstall', { method: 'install' }); this.setMaintainingMessage('call beforeInstall hook...'); await this.emitAsync('beforeInstall', this, options); @@ -1095,12 +1130,14 @@ export class Application exten // Due to the use of custom log levels, // we have to use any type here until Winston updates the type definitions. }) as any; + this.requestLogger = createLogger({ dirname: getLoggerFilePath(this.name), filename: 'request', ...(options?.request || {}), }); - this.sqlLogger = this.createLogger({ + + this._sqlLogger = this.createLogger({ filename: 'sql', level: 'debug', }); @@ -1109,7 +1146,7 @@ export class Application exten protected closeLogger() { this.log?.close(); this.requestLogger?.close(); - this.sqlLogger?.close(); + this._sqlLogger?.close(); } protected init() { @@ -1181,6 +1218,7 @@ export class Application exten group: 'parseVariables', after: 'acl', }); + this._dataSourceManager.use(dataTemplate, { group: 'dataTemplate', after: 'acl' }); this._locales = new Locale(createAppProxy(this)); @@ -1198,6 +1236,10 @@ export class Application exten registerCli(this); this._version = new ApplicationVersion(this); + + for (const callback of Application.staticCommands) { + callback(this); + } } protected createMainDataSource(options: ApplicationOptions) { @@ -1219,15 +1261,26 @@ export class Application exten } protected createDatabase(options: ApplicationOptions) { - const logging = (msg: any) => { + const logging = (...args) => { + let msg = args[0]; + if (typeof msg === 'string') { msg = msg.replace(/[\r\n]/gm, '').replace(/\s+/g, ' '); } + if (msg.includes('INSERT INTO')) { msg = msg.substring(0, 2000) + '...'; } - this.sqlLogger.debug({ message: msg, app: this.name, reqId: this.context.reqId }); + + const content: any = { message: msg, app: this.name, reqId: this.context.reqId }; + + if (args[1] && typeof args[1] === 'number') { + content.executeTime = args[1]; + } + + this._sqlLogger.debug(content); }; + const dbOptions = options.database instanceof Database ? options.database.options : options.database; const db = new Database({ ...dbOptions, diff --git a/packages/core/server/src/gateway/index.ts b/packages/core/server/src/gateway/index.ts index 7f65e72ded..2f0f323565 100644 --- a/packages/core/server/src/gateway/index.ts +++ b/packages/core/server/src/gateway/index.ts @@ -311,7 +311,7 @@ export class Gateway extends EventEmitter { if (!process.env.IS_DEV_CMD) { return; } - const file = resolve(process.cwd(), 'storage/app.watch.ts'); + const file = process.env.WATCH_FILE; if (!fs.existsSync(file)) { await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8'); } diff --git a/packages/core/server/src/helper.ts b/packages/core/server/src/helper.ts index 255df71076..bd8c7885ea 100644 --- a/packages/core/server/src/helper.ts +++ b/packages/core/server/src/helper.ts @@ -16,7 +16,6 @@ import { randomUUID } from 'crypto'; import fs from 'fs'; import i18next from 'i18next'; import bodyParser from 'koa-bodyparser'; -import { resolve } from 'path'; import { createHistogram, RecordableHistogram } from 'perf_hooks'; import Application, { ApplicationOptions } from './application'; import { dataWrapping } from './middlewares/data-wrapping'; @@ -26,7 +25,7 @@ import { i18n } from './middlewares/i18n'; export function createI18n(options: ApplicationOptions) { const instance = i18next.createInstance(); instance.init({ - lng: 'en-US', + lng: process.env.INIT_LANG || 'en-US', resources: {}, keySeparator: false, nsSeparator: false, @@ -40,10 +39,13 @@ export function createResourcer(options: ApplicationOptions) { } export function registerMiddlewares(app: Application, options: ApplicationOptions) { - app.use(async (ctx, next) => { - app.context.reqId = randomUUID(); - await next(); - }); + app.use( + async function generateReqId(ctx, next) { + app.context.reqId = randomUUID(); + await next(); + }, + { tag: 'generateReqId' }, + ); app.use(requestLogger(app.name, app.requestLogger, options.logger?.request), { tag: 'logger' }); @@ -74,7 +76,7 @@ export function registerMiddlewares(app: Application, options: ApplicationOption ); } - app.use(async (ctx, next) => { + app.use(async function getBearerToken(ctx, next) { ctx.getBearerToken = () => { const token = ctx.get('Authorization').replace(/^Bearer\s+/gi, ''); return token || ctx.query.token; @@ -82,10 +84,10 @@ export function registerMiddlewares(app: Application, options: ApplicationOption await next(); }); - app.use(i18n, { tag: 'i18n', after: 'cors' }); + app.use(i18n, { tag: 'i18n', before: 'cors' }); if (options.dataWrapping !== false) { - app.use(dataWrapping(), { tag: 'dataWrapping', after: 'i18n' }); + app.use(dataWrapping(), { tag: 'dataWrapping', after: 'cors' }); } app.use(app.dataSourceManager.middleware(), { tag: 'dataSource', after: 'dataWrapping' }); @@ -121,8 +123,7 @@ export const getCommandFullName = (command: Command) => { /* istanbul ignore next -- @preserve */ export const tsxRerunning = async () => { - const file = resolve(process.cwd(), 'storage/app.watch.ts'); - await fs.promises.writeFile(file, `export const watchId = '${uid()}';`, 'utf-8'); + await fs.promises.writeFile(process.env.WATCH_FILE, `export const watchId = '${uid()}';`, 'utf-8'); }; /* istanbul ignore next -- @preserve */ diff --git a/packages/core/server/src/index.ts b/packages/core/server/src/index.ts index e993b28c56..8e801c1e76 100644 --- a/packages/core/server/src/index.ts +++ b/packages/core/server/src/index.ts @@ -16,6 +16,14 @@ export * from './migration'; export * from './plugin'; export * from './plugin-manager'; export * from './pub-sub-manager'; -export * from './gateway'; -export * from './app-supervisor'; export const OFFICIAL_PLUGIN_PREFIX = '@nocobase/plugin-'; + +export { + appendToBuiltInPlugins, + findAllPlugins, + findBuiltInPlugins, + findLocalPlugins, + packageNameTrim, +} from './plugin-manager/findPackageNames'; + +export { runPluginStaticImports } from './run-plugin-static-imports'; diff --git a/packages/core/server/src/locale/locale.ts b/packages/core/server/src/locale/locale.ts index 4ec017eb00..3f7a39f2c1 100644 --- a/packages/core/server/src/locale/locale.ts +++ b/packages/core/server/src/locale/locale.ts @@ -18,6 +18,7 @@ export interface ResourceStorer { getResources(lang: string): Promise<{ [ns: string]: Record; }>; + reset?: () => Promise; } export class Locale { @@ -45,14 +46,15 @@ export class Locale { name: 'locale', prefix: 'locale', store: 'memory', - max: 2000 }); await this.get(this.defaultLang); } async reload() { - await this.cache.reset(); + const storers = Array.from(this.resourceStorers.getValues()); + const promises = storers.map((storer) => storer.reset()); + await Promise.all([this.cache.reset(), ...promises]); } setLocaleFn(name: string, fn: (lang: string) => Promise) { diff --git a/packages/core/server/src/middlewares/data-template.ts b/packages/core/server/src/middlewares/data-template.ts index 09a5d5c4aa..473645b623 100644 --- a/packages/core/server/src/middlewares/data-template.ts +++ b/packages/core/server/src/middlewares/data-template.ts @@ -10,7 +10,7 @@ import { Context } from '@nocobase/actions'; import { Collection } from '@nocobase/database'; -export const dataTemplate = async (ctx: Context, next) => { +export async function dataTemplate(ctx: Context, next) { const { resourceName, actionName } = ctx.action; const { isTemplate, fields } = ctx.action.params; @@ -22,7 +22,7 @@ export const dataTemplate = async (ctx: Context, next) => { include: fields, }); } -}; +} type TraverseOptions = { collection: Collection; diff --git a/packages/core/server/src/middlewares/i18n.ts b/packages/core/server/src/middlewares/i18n.ts index 877fdb5d78..0637aeef1a 100644 --- a/packages/core/server/src/middlewares/i18n.ts +++ b/packages/core/server/src/middlewares/i18n.ts @@ -19,14 +19,17 @@ export async function i18n(ctx, next) { 'en-US'; return lng; }; + const lng = ctx.getCurrentLocale(); const localeManager = ctx.app.localeManager as Locale; const i18n = await localeManager.getI18nInstance(lng); ctx.i18n = i18n; ctx.t = i18n.t.bind(i18n); + if (lng !== '*' && lng) { - i18n.changeLanguage(lng); + await i18n.changeLanguage(lng); await localeManager.loadResourcesByLang(lng); } + await next(); } diff --git a/packages/core/server/src/middlewares/parse-variables.ts b/packages/core/server/src/middlewares/parse-variables.ts index 109587707f..5e4a5bb655 100644 --- a/packages/core/server/src/middlewares/parse-variables.ts +++ b/packages/core/server/src/middlewares/parse-variables.ts @@ -36,7 +36,7 @@ function isNumeric(str: any) { return !isNaN(str as any) && !isNaN(parseFloat(str)); } -export const parseVariables = async (ctx, next) => { +export async function parseVariables(ctx, next) { const filter = ctx.action.params.filter; if (!filter) { return next(); @@ -66,4 +66,4 @@ export const parseVariables = async (ctx, next) => { }, }); await next(); -}; +} diff --git a/packages/core/server/src/plugin-manager/deps.ts b/packages/core/server/src/plugin-manager/deps.ts index 34bedbcee7..122009869d 100644 --- a/packages/core/server/src/plugin-manager/deps.ts +++ b/packages/core/server/src/plugin-manager/deps.ts @@ -43,7 +43,7 @@ const deps: Record = { i18next: '22.x', 'react-i18next': '11.x', '@dnd-kit/accessibility': '3.x', - '@dnd-kit/core': '5.x', + '@dnd-kit/core': '6.x', '@dnd-kit/modifiers': '6.x', '@dnd-kit/sortable': '6.x', '@dnd-kit/utilities': '3.x', @@ -53,7 +53,7 @@ const deps: Record = { 'pg-hstore': '2.x', sqlite3: '5.x', supertest: '6.x', - axios: '0.26.x', + axios: '1.7.x', '@emotion/css': '11.x', ahooks: '3.x', lodash: '4.x', diff --git a/packages/presets/nocobase/src/server/findPackageNames.ts b/packages/core/server/src/plugin-manager/findPackageNames.ts similarity index 62% rename from packages/presets/nocobase/src/server/findPackageNames.ts rename to packages/core/server/src/plugin-manager/findPackageNames.ts index 912d0e26df..573d53dd8e 100644 --- a/packages/presets/nocobase/src/server/findPackageNames.ts +++ b/packages/core/server/src/plugin-manager/findPackageNames.ts @@ -7,17 +7,17 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { PluginManager } from '@nocobase/server'; import fg from 'fast-glob'; import fs from 'fs-extra'; import _ from 'lodash'; import path from 'path'; +import { PluginManager } from './'; function splitNames(name: string) { return (name || '').split(',').filter(Boolean); } -export async function trim(packageNames: string[]) { +async function trim(packageNames: string[]) { const nameOrPkgs = _.uniq(packageNames).filter(Boolean); const names = []; for (const nameOrPkg of nameOrPkgs) { @@ -32,6 +32,19 @@ export async function trim(packageNames: string[]) { return names; } +const excludes = [ + '@nocobase/plugin-audit-logs', + '@nocobase/plugin-backup-restore', + '@nocobase/plugin-charts', + '@nocobase/plugin-disable-pm-add', + '@nocobase/plugin-mobile-client', + '@nocobase/plugin-mock-collections', + '@nocobase/plugin-multi-app-share-collection', + '@nocobase/plugin-notifications', + '@nocobase/plugin-snapshot-field', + '@nocobase/plugin-workflow-test', +]; + export async function findPackageNames() { const patterns = [ './packages/plugins/*/package.json', @@ -52,23 +65,11 @@ export async function findPackageNames() { return packageJson.name; }), ); - const excludes = [ - '@nocobase/plugin-audit-logs', - '@nocobase/plugin-backup-restore', - '@nocobase/plugin-charts', - '@nocobase/plugin-disable-pm-add', - '@nocobase/plugin-mobile-client', - '@nocobase/plugin-mock-collections', - '@nocobase/plugin-multi-app-share-collection', - '@nocobase/plugin-notifications', - '@nocobase/plugin-snapshot-field', - '@nocobase/plugin-workflow-test', - ]; const nocobasePlugins = await findNocobasePlugins(); const { APPEND_PRESET_BUILT_IN_PLUGINS = '', APPEND_PRESET_LOCAL_PLUGINS = '' } = process.env; return trim( - _.difference(packageNames, excludes) - .filter(Boolean) + packageNames + .filter((pkg) => pkg && !excludes.includes(pkg)) .concat(nocobasePlugins) .concat(splitNames(APPEND_PRESET_BUILT_IN_PLUGINS)) .concat(splitNames(APPEND_PRESET_LOCAL_PLUGINS)), @@ -78,11 +79,18 @@ export async function findPackageNames() { } } +async function getPackageJson() { + const packageJson = await fs.readJson( + path.resolve(process.env.NODE_MODULES_PATH, '@nocobase/preset-nocobase/package.json'), + ); + return packageJson; +} + async function findNocobasePlugins() { try { - const packageJson = await fs.readJson(path.resolve(__dirname, '../../package.json')); + const packageJson = await getPackageJson(); const pluginNames = Object.keys(packageJson.dependencies).filter((name) => name.startsWith('@nocobase/plugin-')); - return trim(pluginNames); + return trim(pluginNames.filter((pkg) => pkg && !excludes.includes(pkg))); } catch (error) { return []; } @@ -91,7 +99,7 @@ async function findNocobasePlugins() { export async function findBuiltInPlugins() { const { APPEND_PRESET_BUILT_IN_PLUGINS = '' } = process.env; try { - const packageJson = await fs.readJson(path.resolve(__dirname, '../../package.json')); + const packageJson = await getPackageJson(); return trim(packageJson.builtIn.concat(splitNames(APPEND_PRESET_BUILT_IN_PLUGINS))); } catch (error) { return []; @@ -103,7 +111,7 @@ export async function findLocalPlugins() { const plugins1 = await findNocobasePlugins(); const plugins2 = await findPackageNames(); const builtInPlugins = await findBuiltInPlugins(); - const packageJson = await fs.readJson(path.resolve(__dirname, '../../package.json')); + const packageJson = await getPackageJson(); const items = await trim( _.difference( plugins1.concat(plugins2).concat(splitNames(APPEND_PRESET_LOCAL_PLUGINS)), @@ -112,3 +120,24 @@ export async function findLocalPlugins() { ); return items; } + +export async function findAllPlugins() { + const builtInPlugins = await findBuiltInPlugins(); + const localPlugins = await findLocalPlugins(); + return _.uniq(builtInPlugins.concat(localPlugins)); +} + +export const packageNameTrim = trim; + +export async function appendToBuiltInPlugins(nameOrPkg: string) { + const APPEND_PRESET_BUILT_IN_PLUGINS = process.env.APPEND_PRESET_BUILT_IN_PLUGINS || ''; + const keys = APPEND_PRESET_BUILT_IN_PLUGINS.split(','); + const { name, packageName } = await PluginManager.parseName(nameOrPkg); + if (keys.includes(packageName)) { + return; + } + if (keys.includes(name)) { + return; + } + process.env.APPEND_PRESET_BUILT_IN_PLUGINS += ',' + nameOrPkg; +} diff --git a/packages/core/server/src/run-plugin-static-imports.ts b/packages/core/server/src/run-plugin-static-imports.ts new file mode 100644 index 0000000000..1f3e8c5d9c --- /dev/null +++ b/packages/core/server/src/run-plugin-static-imports.ts @@ -0,0 +1,24 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ +import { findAllPlugins, PluginManager } from '@nocobase/server'; + +export async function runPluginStaticImports() { + const packages = await findAllPlugins(); + for (const name of packages) { + const { packageName } = await PluginManager.parseName(name); + try { + const plugin = require(packageName); + if (plugin && plugin.staticImport) { + await plugin.staticImport(); + } + } catch (error) { + continue; + } + } +} diff --git a/packages/core/telemetry/README.md b/packages/core/telemetry/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/telemetry/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/telemetry/package.json b/packages/core/telemetry/package.json index 197890a26c..cf07b704cc 100644 --- a/packages/core/telemetry/package.json +++ b/packages/core/telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/telemetry", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "nocobase telemetry library", "license": "AGPL-3.0", "main": "./lib/index.js", @@ -11,7 +11,7 @@ "directory": "packages/telemetry" }, "dependencies": { - "@nocobase/utils": "1.4.0-alpha", + "@nocobase/utils": "1.4.0-alpha.11", "@opentelemetry/api": "^1.7.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/resources": "^1.19.0", diff --git a/packages/core/test/README.md b/packages/core/test/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/test/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/test/package.json b/packages/core/test/package.json index 11642c491e..8e386fdb35 100644 --- a/packages/core/test/package.json +++ b/packages/core/test/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/test", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "lib/index.js", "module": "./src/index.ts", "types": "./lib/index.d.ts", @@ -51,7 +51,7 @@ }, "dependencies": { "@faker-js/faker": "8.1.0", - "@nocobase/server": "1.4.0-alpha", + "@nocobase/server": "1.4.0-alpha.11", "@playwright/test": "^1.45.3", "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.0.0", @@ -62,7 +62,7 @@ "@vitest/coverage-istanbul": "^1.5.0", "@vitest/coverage-v8": "^1.5.0", "axios-mock-adapter": "1.22.0", - "jsdom": "^16.0.0", + "jsdom": "^25.0.1", "jsdom-worker": "^0.3.0", "mariadb": "^2.5.6", "mockjs": "^1.1.0", diff --git a/packages/core/test/src/e2e/e2eUtils.ts b/packages/core/test/src/e2e/e2eUtils.ts index 75f7141e28..a52a74904c 100644 --- a/packages/core/test/src/e2e/e2eUtils.ts +++ b/packages/core/test/src/e2e/e2eUtils.ts @@ -181,6 +181,8 @@ export interface PageConfig { pageSchema?: any; /** 如果为 true 则表示不会更改 PageSchema 的 uid */ keepUid?: boolean; + /** 在 URL 中的 uid,例如:/admin/0ig6xhe03u2 */ + pageUid?: string; } export interface MobilePageConfig extends Omit { @@ -199,6 +201,8 @@ interface CreatePageOptions { pageSchema?: any; /** 如果为 true 则表示不会更改 PageSchema 的 uid */ keepUid?: boolean; + /** 在 URL 中的 uid,例如:/admin/0ig6xhe03u2 */ + pageUid?: string; } interface CreateMobilePageOptions extends Omit { @@ -346,6 +350,7 @@ export class NocoPage { pageSchema: this.options?.pageSchema, url: this.options?.url, keepUid: this.options?.keepUid, + pageUid: this.options?.pageUid, }), ); @@ -706,7 +711,7 @@ const updateUidOfPageSchema = (uiSchema: any) => { * 在 NocoBase 中创建一个页面 */ const createPage = async (options?: CreatePageOptions) => { - const { type = 'page', url, name, pageSchema, keepUid } = options || {}; + const { type = 'page', url, name, pageSchema, keepUid, pageUid: pageUidFromOptions } = options || {}; const api = await request.newContext({ storageState: process.env.PLAYWRIGHT_AUTH_FILE, }); @@ -728,7 +733,7 @@ const createPage = async (options?: CreatePageOptions) => { }; const state = await api.storageState(); const headers = getHeaders(state); - const pageUid = uid(); + const pageUid = pageUidFromOptions || uid(); const gridName = uid(); const result = await api.post(`/api/uiSchemas:insertAdjacent/nocobase-admin-menu?position=beforeEnd`, { diff --git a/packages/core/test/src/e2e/templatesOfPage.ts b/packages/core/test/src/e2e/templatesOfPage.ts index 7d1cbe6e87..1831801496 100644 --- a/packages/core/test/src/e2e/templatesOfPage.ts +++ b/packages/core/test/src/e2e/templatesOfPage.ts @@ -12422,6 +12422,360 @@ export const oneFilterFormBlockWithAllAssociationFields: PageConfig = { }, }; +/** + * v1.3.33-beta + * 页面中有一个 filter form 区块,包含所有关系字段类型的字段 + */ +export const oneFilterFormBlockWithAllAssociationFieldsV1333Beta: PageConfig = { + collections: generalWithAssociation, + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-index': 1, + properties: { + wwjstoiggum: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-index': 1, + properties: { + veez0d6lmes: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + aoeb96c9io9: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + bddrxac1sy8: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'FilterFormBlockProvider', + 'x-use-decorator-props': 'useFilterFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'general', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:filterForm', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.33-beta', + properties: { + ia80x2ee6jk: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useFilterFormBlockProps', + 'x-app-version': '1.3.33-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'filterForm:configureFields', + 'x-app-version': '1.3.33-beta', + properties: { + osddczg4sa4: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + '6smjnnnol7d': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + oneToOneBelongsTo: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + required: false, + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FilterFormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-use-decorator-props': 'useFormItemProps', + 'x-collection-field': 'general.oneToOneBelongsTo', + 'x-component-props': { + multiple: false, + fieldNames: { + label: 'id', + value: 'id', + }, + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': 'ru1rkpoj58o', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'rn702pnhtfr', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '78o33btjnxv', + 'x-async': false, + 'x-index': 1, + }, + rui9epb6050: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + '91j7vnoy7kn': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + oneToOneHasOne: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + required: false, + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FilterFormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-use-decorator-props': 'useFormItemProps', + 'x-collection-field': 'general.oneToOneHasOne', + 'x-component-props': { + multiple: false, + fieldNames: { + label: 'id', + value: 'id', + }, + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': 'mflntqm2g5h', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'xmo1kdyv438', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5069bfzim4k', + 'x-async': false, + 'x-index': 2, + }, + xljcz69cnra: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + yxbfetadlnk: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + oneToMany: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + required: false, + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FilterFormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-use-decorator-props': 'useFormItemProps', + 'x-collection-field': 'general.oneToMany', + 'x-component-props': { + multiple: true, + fieldNames: { + label: 'id', + value: 'id', + }, + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': 'hfehr0sfzvf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '3pmxr13mgiz', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '0rtq29eic2v', + 'x-async': false, + 'x-index': 3, + }, + xbd07gapqj0: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + '55v9zbubqcy': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + manyToOne: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + required: false, + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FilterFormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-use-decorator-props': 'useFormItemProps', + 'x-collection-field': 'general.manyToOne', + 'x-component-props': { + multiple: false, + fieldNames: { + label: 'id', + value: 'id', + }, + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': 'w0ugawa0dxk', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'vpkn40vetcq', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9x163mjqgix', + 'x-async': false, + 'x-index': 4, + }, + '6mgr3hv8s7d': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.33-beta', + properties: { + ojxf20yrv33: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.33-beta', + properties: { + manyToMany: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + required: false, + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FilterFormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-use-decorator-props': 'useFormItemProps', + 'x-collection-field': 'general.manyToMany', + 'x-component-props': { + multiple: true, + fieldNames: { + label: 'id', + value: 'id', + }, + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': 'klhocltvq6v', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'frzc0g87myc', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5mm4gyqqa90', + 'x-async': false, + 'x-index': 5, + }, + }, + 'x-uid': '4ky8q78hske', + 'x-async': false, + 'x-index': 1, + }, + z51e14s05s5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'filterForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + float: 'right', + }, + }, + 'x-app-version': '1.3.33-beta', + 'x-uid': 'whjwk4sh4no', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '4pzmjj93o9l', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'v4k9k62avgb', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4sq6xto3yu9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'mf2few2trjl', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'xb5aal7tne6', + 'x-async': false, + }, + }, + 'x-uid': 'n6tnlu0v6ct', + 'x-async': true, + }, +}; + /** * 1. 一个 Table 区块 * 2. 点击 Add new 有一个 Form 区块 diff --git a/packages/core/utils/README.md b/packages/core/utils/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/core/utils/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index 83f3327ad5..9c7f3d4a59 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -1,16 +1,18 @@ { "name": "@nocobase/utils", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "lib/index.js", "types": "./lib/index.d.ts", "license": "AGPL-3.0", "dependencies": { + "@budibase/handlebars-helpers": "^0.14.0", "@hapi/topo": "^6.0.0", "@rc-component/mini-decimal": "^1.1.0", "dayjs": "^1.11.9", "deepmerge": "^4.2.2", "flat-to-nested": "^1.1.1", "graphlib": "^2.1.8", + "handlebars": "^4.7.8", "multer": "^1.4.5-lts.1", "object-path": "^0.11.8" }, diff --git a/packages/core/utils/src/client.ts b/packages/core/utils/src/client.ts index c58b8e32fc..a7594ed60a 100644 --- a/packages/core/utils/src/client.ts +++ b/packages/core/utils/src/client.ts @@ -15,6 +15,7 @@ export * from './common'; export * from './date'; export * from './forEach'; export * from './getValuesByPath'; +export * from './handlebars'; export * from './isValidFilter'; export * from './json-templates'; export * from './log'; diff --git a/packages/core/utils/src/date.ts b/packages/core/utils/src/date.ts index 731c23a1f9..d35fe611e3 100644 --- a/packages/core/utils/src/date.ts +++ b/packages/core/utils/src/date.ts @@ -78,7 +78,7 @@ const toMoment = (val: any, options?: Str2momentOptions) => { if (!val) { return; } - const offset = options.utcOffset || -1 * new Date().getTimezoneOffset(); + const offset = options.utcOffset !== undefined ? options.utcOffset : -1 * new Date().getTimezoneOffset(); const { gmt, picker, utc = true } = options; if (dayjs(val).isValid()) { if (!utc) { diff --git a/packages/core/utils/src/handlebars.ts b/packages/core/utils/src/handlebars.ts new file mode 100644 index 0000000000..373fbab172 --- /dev/null +++ b/packages/core/utils/src/handlebars.ts @@ -0,0 +1,48 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import url from 'url'; +import Handlebars from 'handlebars'; +import helpers from '@budibase/handlebars-helpers'; +import _ from 'lodash'; + +import { dayjs } from './dayjs'; + +const allHelpers = helpers(); + +//遍历所有 helper 并手动注册到 Handlebars +Object.keys(allHelpers).forEach(function (helperName) { + Handlebars.registerHelper(helperName, allHelpers[helperName]); +}); +// 自定义 helper 来处理对象 +Handlebars.registerHelper('json', function (context) { + return JSON.stringify(context); +}); + +//重写urlParse +Handlebars.registerHelper('urlParse', function (str) { + try { + return JSON.stringify(url.parse(str)); + } catch (error) { + return `Invalid URL: ${str}`; + } +}); + +Handlebars.registerHelper('dateFormat', (date, format, tz) => { + if (typeof tz === 'string') { + return dayjs(date).tz(tz).format(format); + } + return dayjs(date).format(format); +}); + +Handlebars.registerHelper('isNull', (value) => { + return _.isNull(value); +}); + +export { Handlebars }; diff --git a/packages/core/utils/src/index.ts b/packages/core/utils/src/index.ts index 7f20a0f15b..fb8a5cb474 100644 --- a/packages/core/utils/src/index.ts +++ b/packages/core/utils/src/index.ts @@ -17,6 +17,7 @@ export * from './date'; export * from './dayjs'; export * from './forEach'; export * from './fs-exists'; +export * from './handlebars'; export * from './isValidFilter'; export * from './json-templates'; export * from './koa-multer'; @@ -34,6 +35,7 @@ export * from './toposort'; export * from './uid'; export * from './url'; export * from './i18n'; +export * from './wrap-middleware'; export { dayjs, lodash }; export { Schema } from '@formily/json-schema'; diff --git a/packages/core/utils/src/wrap-middleware.ts b/packages/core/utils/src/wrap-middleware.ts new file mode 100644 index 0000000000..820ba15c25 --- /dev/null +++ b/packages/core/utils/src/wrap-middleware.ts @@ -0,0 +1,36 @@ +export function wrapMiddlewareWithLogging(fn, logger?) { + if (process.env['LOGGER_LEVEL'] !== 'trace') { + return fn; + } + + const name = fn.name || fn.toString().slice(0, 100); + + return async (ctx, next) => { + const reqId = ctx.reqId; + + if (!logger && !ctx.logger) { + return await fn(ctx, next); + } + + if (!logger && ctx.logger) { + logger = ctx.logger; + } + + logger.trace(`--> Entering middleware: ${name}`, { reqId }); + + const start = Date.now(); + + await fn(ctx, async () => { + const beforeNext = Date.now(); + logger.trace(`--> Before next middleware: ${name} - ${beforeNext - start}ms`, { reqId }); + + await next(); + + const afterNext = Date.now(); + logger.trace(`<-- After next middleware: ${name} - ${afterNext - beforeNext}ms`, { reqId }); + }); + + const ms = Date.now() - start; + logger.trace(`<-- Exiting middleware: ${name} - ${ms}ms`, { reqId }); + }; +} diff --git a/packages/plugins/@nocobase/plugin-acl/README.md b/packages/plugins/@nocobase/plugin-acl/README.md index af0dd46bba..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-acl/README.md +++ b/packages/plugins/@nocobase/plugin-acl/README.md @@ -1,11 +1,30 @@ -# acl +# NocoBase -English | [中文](./README.zh-CN.md) + -基于角色的权限控制插件。 -## 安装激活 +## What is NocoBase -内置插件无需手动安装激活。 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 使用方法 +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-acl/README.zh-CN.md b/packages/plugins/@nocobase/plugin-acl/README.zh-CN.md deleted file mode 100644 index 8260a114b8..0000000000 --- a/packages/plugins/@nocobase/plugin-acl/README.zh-CN.md +++ /dev/null @@ -1,11 +0,0 @@ -# acl - -[English](./README.md) | 中文 - -基于角色的权限控制插件。 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-acl/package.json b/packages/plugins/@nocobase/plugin-acl/package.json index 11c82472d1..0004abd1ee 100644 --- a/packages/plugins/@nocobase/plugin-acl/package.json +++ b/packages/plugins/@nocobase/plugin-acl/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "权限控制", "description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.", "description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/acl", diff --git a/packages/plugins/@nocobase/plugin-acl/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-acl/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-acl/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-acl/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-acl/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-acl/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-acl/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-acl/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/actions.test.ts index 7e542650ce..a8f3b95d35 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/actions.test.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/__tests__/actions.test.ts @@ -184,7 +184,7 @@ describe('destroy action with acl', () => { expect(response.statusCode).toEqual(403); }); - it('should throw error when user has no permissions with array query', async () => { + it.skip('should throw error when user has no permissions with array query', async () => { const userRole = app.acl.define({ role: 'user', }); @@ -241,9 +241,10 @@ describe('destroy action with acl', () => { }, }); - // should throw error expect(response.statusCode).toEqual(403); + expect(await Post.repository.count()).toBe(6); + const response2 = await app .agent() .resource('posts') @@ -253,7 +254,6 @@ describe('destroy action with acl', () => { }, }); - // should throw error expect(response2.statusCode).toEqual(200); }); }); diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts b/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts index 6249c5a73e..11e2411689 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/middlewares/with-acl-meta.ts @@ -43,6 +43,11 @@ function createWithACLMetaMiddleware() { const Model = collection.model; + // skip if collection is multi filter target key + if (collection.isMultiFilterTargetKey()) { + return; + } + // @ts-ignore const primaryKeyField = Model.primaryKeyField || Model.primaryKeyAttribute; @@ -136,6 +141,11 @@ function createWithACLMetaMiddleware() { return listData.map((item) => item[primaryKeyField]); })(); + // if all ids are empty, skip + if (ids.filter(Boolean).length == 0) { + return; + } + const conditions = []; const allAllowed = []; diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts index 5dd78bafc2..7856b23384 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts @@ -450,7 +450,7 @@ export class PluginACLServer extends Plugin { }; }); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function showAnonymous(ctx, next) { const { actionName, resourceName, params } = ctx.action; const { showAnonymous } = params || {}; if (actionName === 'list' && resourceName === 'roles') { @@ -531,7 +531,6 @@ export class PluginACLServer extends Plugin { } }); - // throw error when user has no fixed params permissions this.app.acl.use( async (ctx: any, next) => { const action = ctx.permission?.can?.action; @@ -544,6 +543,15 @@ export class PluginACLServer extends Plugin { return; } + const hasFilterByTk = (params) => { + return JSON.stringify(params).includes('filterByTk'); + }; + + if (!hasFilterByTk(ctx.permission.mergedParams) || !hasFilterByTk(ctx.permission.rawParams)) { + await next(); + return; + } + // params after merge with fixed params const filteredCount = await repository.count(ctx.permission.mergedParams); @@ -568,7 +576,7 @@ export class PluginACLServer extends Plugin { // append allowedActions to list & get response this.app.use( - async (ctx, next) => { + async function withACLMetaMiddleware(ctx, next) { try { await withACLMeta(ctx, next); } catch (error) { diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/README.md b/packages/plugins/@nocobase/plugin-action-bulk-edit/README.md index 51e4a94b78..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/README.md +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-action-bulk-edit +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json index 06eea94861..55f289ad01 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-bulk-edit", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-bulk-edit", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit", diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditFormItemInitializers.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditFormItemInitializers.tsx index 1e609eeec8..8ee11e824c 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditFormItemInitializers.tsx +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/BulkEditFormItemInitializers.tsx @@ -27,7 +27,7 @@ const commonOptions = { }, { name: 'addText', - title: '{{t("Add text")}}', + title: '{{t("Add Markdown")}}', Component: 'BlockItemInitializer', schema: { type: 'void', diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/popup.test.ts b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/popup.test.ts new file mode 100644 index 0000000000..3531d0e9e8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/popup.test.ts @@ -0,0 +1,32 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { expect, test } from '@nocobase/test/e2e'; +import { theAddBlockButtonInDrawerShouldBeVisible } from './utils'; + +test.describe('popup of bulk edit', () => { + test('the Add block button in drawer should be visible', async ({ page, mockPage }) => { + await mockPage(theAddBlockButtonInDrawerShouldBeVisible).goto(); + + // open subpage, anb then open the bulk edit drawer, the Add block in drawer should be visible + await page.getByLabel('action-Action.Link-open').click(); + await page.getByLabel('action-Action-Bulk edit 1-').click(); + await expect( + page.getByTestId('drawer-Action.Container-users-Bulk edit').getByLabel('schema-initializer-Grid-popup'), + ).toBeVisible(); + await page.getByLabel('drawer-Action.Container-users-Bulk edit-mask').click(); + + // click other tab, then open the bulk edit drawer, the Add block in drawer should be visible + await page.getByText('new tab').click(); + await page.getByLabel('action-Action-Bulk edit 2-').click(); + await expect( + page.getByTestId('drawer-Action.Container-users-Bulk edit').getByLabel('schema-initializer-Grid-popup'), + ).toBeVisible(); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/utils.ts b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/utils.ts index 9fd147b4b0..3e67dc3ad2 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/utils.ts +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/__e2e__/utils.ts @@ -541,3 +541,705 @@ export const oneEmptyTableBlockWithCustomizeActions: PageConfig = { 'x-async': true, }, }; + +export const theAddBlockButtonInDrawerShouldBeVisible = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-app-version': '1.3.32-beta', + properties: { + ydtgms2lmr4: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-app-version': '1.3.32-beta', + properties: { + jk8ix7ivmvb: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.32-beta', + properties: { + fdohqzh304u: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.32-beta', + properties: { + syp961jrbvs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.32-beta', + 'x-uid': 'd9ciqysn98f', + 'x-async': false, + 'x-index': 1, + }, + bflk6vcqbda: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.32-beta', + properties: { + '9fy397o5n7x': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.32-beta', + properties: { + ujxoicj1do2: { + 'x-uid': 'stie1dytwk9', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'open subpage', + 'x-action': 'view', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:view', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'page', + iconColor: '#1677FF', + danger: false, + }, + 'x-action-context': { + dataSource: 'main', + collection: 'users', + }, + 'x-decorator': 'ACLActionProvider', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + properties: { + gkbkej9bcfg: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.32-beta', + properties: { + qsnxw1kyglw: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.32-beta', + properties: { + rg5p0jxa04f: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + yrh6fyeotth: { + 'x-uid': 'm9eykj7hcac', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'Bulk edit 1', + 'x-component': 'Action', + 'x-action': 'customize:bulkEdit', + 'x-action-settings': { + updateMode: 'selected', + }, + 'x-component-props': { + openMode: 'drawer', + icon: 'EditOutlined', + iconColor: '#1677FF', + danger: false, + type: 'default', + }, + 'x-align': 'right', + 'x-decorator': 'BulkEditActionDecorator', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:bulkEdit', + 'x-acl-action': 'update', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-app-version': '1.3.32-beta', + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Bulk edit")}}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + 'x-app-version': '1.3.32-beta', + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + 'x-initializer-props': { + gridInitializer: + 'popup:bulkEdit:addBlock', + }, + 'x-app-version': '1.3.32-beta', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Bulk edit")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': '1.3.32-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': + 'popup:bulkEdit:addBlock', + 'x-app-version': '1.3.32-beta', + 'x-uid': '86kvljipurj', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'osjgnzq0bxu', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'nddbbs6e5vc', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'r604iwattq7', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kdyyiruax36', + 'x-async': false, + 'x-index': 1, + }, + lodqxwvvrwu: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.32-beta', + properties: { + '1gt1zh3wlhl': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.32-beta', + 'x-uid': 'r3rllrh7wzk', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'dmx697o309f', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'labwg68zvn6', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '0fn0tjzur2c', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'goinlb9fan9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '74qg393u7hr', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hetu8763puj', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4dn6kb0cmwp', + 'x-async': false, + 'x-index': 1, + }, + gljvwj6dtl8: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'new tab', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': '1.3.32-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + 'x-app-version': '1.3.32-beta', + properties: { + c1yhos175hj: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.32-beta', + properties: { + yhbbftd4j86: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.32-beta', + properties: { + '4y4cdnzx645': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + vp75269u5ai: { + 'x-uid': '3dup1gvthik', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'Bulk edit 2', + 'x-component': 'Action', + 'x-action': 'customize:bulkEdit', + 'x-action-settings': { + updateMode: 'selected', + }, + 'x-component-props': { + openMode: 'drawer', + icon: 'EditOutlined', + iconColor: '#1677FF', + danger: false, + type: 'default', + }, + 'x-align': 'right', + 'x-decorator': 'BulkEditActionDecorator', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:bulkEdit', + 'x-acl-action': 'update', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-app-version': '1.3.32-beta', + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Bulk edit")}}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + 'x-app-version': '1.3.32-beta', + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + 'x-initializer-props': { + gridInitializer: + 'popup:bulkEdit:addBlock', + }, + 'x-app-version': '1.3.32-beta', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Bulk edit")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': '1.3.32-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': + 'popup:bulkEdit:addBlock', + 'x-app-version': '1.3.32-beta', + 'x-uid': 'ev7zy0yictp', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'n6f6k6fvukf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'xm9xscjd3am', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'tpukizpyz29', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'mf55d2cpdpx', + 'x-async': false, + 'x-index': 1, + }, + p5i670h31zl: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.32-beta', + properties: { + owyte0l5cgt: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.32-beta', + 'x-uid': 'bvc3y7y3fem', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4xso516rjk9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'dyyuo828wdo', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'um86bfrnd0n', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'bwkvo3771d1', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8ecnnc4yvgz', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '6pmwqi2etjn', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'e88jnt5tv4e', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '2u9lxm7qivt', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kqabk7m6d7h', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'eqyqzt5letc', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7t4bewrinmr', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ouboqexioby', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'e363l9dbg2g', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9p4tvagqpfd', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'a68euip3d3s', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hklfkdijm58', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '536m19repzv', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts index 87069a771f..b37491008b 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/bulkEditFormItemSettings.ts @@ -22,6 +22,7 @@ import { useValidateSchema, fieldComponentSettingsItem, EditValidationRules, + useCompile, } from '@nocobase/client'; import _ from 'lodash'; import { useTranslation } from 'react-i18next'; @@ -46,6 +47,7 @@ export const bulkEditFormItemSettings = new SchemaSettings({ type: 'modal', useComponentProps() { const { t } = useTranslation(); + const compile = useCompile(); const { dn } = useDesignable(); const field = useField(); const fieldSchema = useFieldSchema(); @@ -70,16 +72,16 @@ export const bulkEditFormItemSettings = new SchemaSettings({ }, }, onSubmit({ title }) { - if (title) { - field.title = title; - fieldSchema.title = title; - dn.emit('patch', { - schema: { - 'x-uid': fieldSchema['x-uid'], - title: fieldSchema.title, - }, - }); - } + const result = title.trim() === '' ? collectionField?.uiSchema?.title : title; + field.title = compile(result); + fieldSchema.title = title; + dn.emit('patch', { + schema: { + 'x-uid': fieldSchema['x-uid'], + title: fieldSchema.title, + }, + }); + dn.refresh(); }, }; diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx index 0ed93b3552..c5f82ae6fa 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/client/component/BulkEditField.tsx @@ -17,7 +17,6 @@ import { useCollection_deprecated, useCompile, useComponent, - useFormBlockContext, } from '@nocobase/client'; import { Checkbox, Select, Space } from 'antd'; import React, { useEffect, useState } from 'react'; @@ -36,14 +35,6 @@ const InternalField: React.FC = (props) => { const setFieldProps = (key, value) => { field[key] = typeof field[key] === 'undefined' ? value : field[key]; }; - const ctx = useFormBlockContext(); - - useEffect(() => { - if (ctx?.field) { - ctx.field.added = ctx.field.added || new Set(); - ctx.field.added.add(fieldSchema.name); - } - }); useEffect(() => { if (!uiSchema) { diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-action-bulk-edit/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/README.md b/packages/plugins/@nocobase/plugin-action-bulk-update/README.md index 74a21eb41d..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-update/README.md +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-action-bulk-update +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json index f511dd6f66..adadf73b44 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-bulk-update", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-bulk-update", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update", diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/en-US.json new file mode 100644 index 0000000000..d02f52468e --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/en-US.json @@ -0,0 +1,5 @@ +{ + "Bulk update": "Bulk update", + "After successful bulk update": "After successful bulk update", + "Please select the records to be updated": "Please select the records to be updated" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/es-ES.json b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/es-ES.json new file mode 100644 index 0000000000..4fdd417f75 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/es-ES.json @@ -0,0 +1,5 @@ +{ + "Bulk update": "Actualización en masa", + "After successful bulk update": "Tras una actualización en masa correcta", + "Please select the records to be updated": "Seleccione los registros que desea actualizar" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ja-JP.json new file mode 100644 index 0000000000..574fb48ebf --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ja-JP.json @@ -0,0 +1,6 @@ +{ + "Bulk update": "一括更新", + "After successful bulk update": "一括更新が完了しました", + "Please select the records to be updated": "更新するレコードを選択してください", + "Entire collection": "全コレクション" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ja_JP.ts b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ja_JP.ts deleted file mode 100644 index 0ca4ee3e8a..0000000000 --- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ja_JP.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - "Bulk update": "一括更新", - "After successful bulk update": "一括更新が完了しました", - "Please select the records to be updated": "更新するレコードを選択してください", - "Entire collection": "全コレクション", -}; \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ko-KR.json b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ko-KR.json new file mode 100644 index 0000000000..d8b1cec7af --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ko-KR.json @@ -0,0 +1,5 @@ +{ + "Bulk update": "대량 업데이트", + "After successful bulk update": "대량 업데이트 성공 후", + "Please select the records to be updated": "업데이트할 레코드를 선택하십시오" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ko_KR.ts b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ko_KR.ts deleted file mode 100644 index 50f1355808..0000000000 --- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/ko_KR.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'Bulk update': '대량 업데이트', - 'After successful bulk update': '대량 업데이트 성공 후', - 'Please select the records to be updated': '업데이트할 레코드를 선택하십시오' -}; diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/pt-BR.json b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/pt-BR.json new file mode 100644 index 0000000000..fb889d8d37 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/pt-BR.json @@ -0,0 +1,5 @@ +{ + "Bulk update": "Atualização em massa", + "After successful bulk update": "Após a atualização em massa bem sucedida", + "Please select the records to be updated": "Por favor, selecione os registros a serem atualizados" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/zh-CN.json new file mode 100644 index 0000000000..485a13aabb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/zh-CN.json @@ -0,0 +1,6 @@ +{ + "Bulk update": "批量更新", + "After successful bulk update": "批量成功更新后", + "Please select the records to be updated": "请选择要更新的记录", + "Entire collection": "全表" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/package.json b/packages/plugins/@nocobase/plugin-action-custom-request/package.json index 9467e68bce..42c1d641dc 100644 --- a/packages/plugins/@nocobase/plugin-action-custom-request/package.json +++ b/packages/plugins/@nocobase/plugin-action-custom-request/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-custom-request", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-custom-request", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request", diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-action-custom-request/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/README.md b/packages/plugins/@nocobase/plugin-action-duplicate/README.md index 9ef1e61ef9..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-action-duplicate/README.md +++ b/packages/plugins/@nocobase/plugin-action-duplicate/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-action-duplicate +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/package.json b/packages/plugins/@nocobase/plugin-action-duplicate/package.json index d21fe9a365..47776d6824 100644 --- a/packages/plugins/@nocobase/plugin-action-duplicate/package.json +++ b/packages/plugins/@nocobase/plugin-action-duplicate/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-duplicate", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-duplicate", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate", diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx index 95907e04f7..dfcd999ac7 100644 --- a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx +++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/DuplicateAction.tsx @@ -15,6 +15,7 @@ import { FormBlockContext, PopupSettingsProvider, RecordProvider, + TabsContextProvider, fetchTemplateData, useACLActionParamsContext, useAPIClient, @@ -200,16 +201,19 @@ export const DuplicateAction = observer( {loading ? t('Duplicating') : children || t('Duplicate')} )} - - {/* 这里的 record 就是弹窗中创建表单的 sourceRecord */} - - - - - - - - + {/* Clear the context of Tabs to avoid affecting the Tabs of the upper-level popup */} + + + {/* 这里的 record 就是弹窗中创建表单的 sourceRecord */} + + + + + + + + +
diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/__e2e__/popup.test.ts b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/__e2e__/popup.test.ts new file mode 100644 index 0000000000..4e64200cf5 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/__e2e__/popup.test.ts @@ -0,0 +1,28 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { expect, test } from '@nocobase/test/e2e'; +import { theAddBlockButtonInDrawerShouldBeVisible } from './templates'; + +test.describe('popup of duplicate', () => { + test('the Add block button in drawer should be visible', async ({ page, mockPage }) => { + await mockPage(theAddBlockButtonInDrawerShouldBeVisible).goto(); + + // open subpage, anb then open the duplicate drawer, the Add block in drawer should be visible + await page.getByLabel('action-Action.Link-open').click(); + await page.getByLabel('action-Action.Link-Duplicate').click(); + await expect(page.getByLabel('block-item-Markdown.Void-').getByText('Duplicate markdown 1.')).toBeVisible(); + await page.getByLabel('drawer-Action.Container-users-Duplicate-mask').click(); + + // click other tab, then open the bulk edit drawer, the Add block in drawer should be visible + await page.getByText('new tab').click(); + await page.getByLabel('action-Action.Link-Duplicate').click(); + await expect(page.getByLabel('block-item-Markdown.Void-').getByText('Duplicate markdown 2.')).toBeVisible(); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/__e2e__/templates.ts b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/__e2e__/templates.ts index b590fa9770..ff41fa6c6e 100644 --- a/packages/plugins/@nocobase/plugin-action-duplicate/src/client/__e2e__/templates.ts +++ b/packages/plugins/@nocobase/plugin-action-duplicate/src/client/__e2e__/templates.ts @@ -1039,3 +1039,1031 @@ export const T4546 = { 'x-async': true, }, }; +export const theAddBlockButtonInDrawerShouldBeVisible = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-app-version': '1.3.32-beta', + properties: { + ydtgms2lmr4: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + 'x-app-version': '1.3.32-beta', + properties: { + jk8ix7ivmvb: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.32-beta', + properties: { + fdohqzh304u: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.32-beta', + properties: { + syp961jrbvs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.32-beta', + 'x-uid': 'd9ciqysn98f', + 'x-async': false, + 'x-index': 1, + }, + bflk6vcqbda: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.32-beta', + properties: { + '9fy397o5n7x': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.32-beta', + properties: { + ujxoicj1do2: { + 'x-uid': 'stie1dytwk9', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'open subpage', + 'x-action': 'view', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:view', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'page', + iconColor: '#1677FF', + danger: false, + }, + 'x-action-context': { + dataSource: 'main', + collection: 'users', + }, + 'x-decorator': 'ACLActionProvider', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + properties: { + gkbkej9bcfg: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.32-beta', + properties: { + qsnxw1kyglw: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.32-beta', + properties: { + rg5p0jxa04f: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.32-beta', + 'x-uid': 'kdyyiruax36', + 'x-async': false, + 'x-index': 1, + }, + lodqxwvvrwu: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.32-beta', + properties: { + '1gt1zh3wlhl': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.32-beta', + properties: { + ertbfj6rncg: { + 'x-uid': '06gv2hdo5gn', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-action': 'duplicate', + 'x-acl-action': 'create', + title: 'Duplicate 1', + 'x-component': 'Action.Link', + 'x-decorator': + 'DuplicateActionDecorator', + 'x-component-props': { + openMode: 'drawer', + component: 'DuplicateAction', + type: 'primary', + duplicateMode: 'continueduplicate', + duplicateFields: ['nickname'], + duplicateCollection: 'users', + iconColor: '#1677FF', + danger: false, + }, + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': + 'actionSettings:duplicate', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Duplicate") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Duplicate")}}', + 'x-component': + 'Tabs.TabPane', + 'x-designer': + 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': + 'popup:addNew:addBlock', + properties: { + '8688wl2mr4i': { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Row', + 'x-app-version': + '1.3.32-beta', + properties: { + f91h0y067hv: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.32-beta', + properties: { + pdsymaj9tip: { + 'x-uid': + 'sdya5bkm9c8', + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-settings': + 'blockSettings:markdown', + 'x-decorator': + 'CardItem', + 'x-decorator-props': + { + name: 'markdown', + engine: + 'handlebars', + }, + 'x-component': + 'Markdown.Void', + 'x-editable': + false, + 'x-component-props': + { + content: + 'Duplicate markdown 1.', + }, + 'x-app-version': + '1.3.32-beta', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '5r3hc64n1ej', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '774qbcnt3qu', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '3v32xcbwr7m', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '0wttna5fpai', + 'x-async': false, + 'x-index': 1, + }, + wz1yhvrpmrv: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'new tab in drawer', + 'x-component': + 'Tabs.TabPane', + 'x-designer': + 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': + '1.3.32-beta', + properties: { + grid: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': + 'popup:common:addBlock', + 'x-app-version': + '1.3.32-beta', + properties: { + y5zjil8oaa3: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Row', + 'x-app-version': + '1.3.32-beta', + properties: { + v0gws5adwan: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.32-beta', + properties: { + igm7sdsm3ur: { + 'x-uid': + 'estddzpckrg', + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-settings': + 'blockSettings:markdown', + 'x-decorator': + 'CardItem', + 'x-decorator-props': + { + name: 'markdown', + engine: + 'handlebars', + }, + 'x-component': + 'Markdown.Void', + 'x-editable': + false, + 'x-component-props': + { + content: + 'new tab in drawer markdown 1.', + }, + 'x-app-version': + '1.3.32-beta', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'r0ujr6t1rc1', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'veneg38qs5f', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8mng9basz36', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'cmetycyfemg', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '7nlcloha97l', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '22om0z59x41', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'r3rllrh7wzk', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'dmx697o309f', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'labwg68zvn6', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '0fn0tjzur2c', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'goinlb9fan9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '74qg393u7hr', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hetu8763puj', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4dn6kb0cmwp', + 'x-async': false, + 'x-index': 1, + }, + gljvwj6dtl8: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'new tab', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': '1.3.32-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + 'x-app-version': '1.3.32-beta', + properties: { + c1yhos175hj: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.32-beta', + properties: { + yhbbftd4j86: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.32-beta', + properties: { + '4y4cdnzx645': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.32-beta', + 'x-uid': 'mf55d2cpdpx', + 'x-async': false, + 'x-index': 1, + }, + p5i670h31zl: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.32-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.32-beta', + properties: { + owyte0l5cgt: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.32-beta', + properties: { + loi6el5pg6g: { + 'x-uid': 'a6072zadr0v', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-action': 'duplicate', + 'x-acl-action': 'create', + title: 'Duplicate 2', + 'x-component': 'Action.Link', + 'x-decorator': + 'DuplicateActionDecorator', + 'x-component-props': { + openMode: 'drawer', + component: 'DuplicateAction', + type: 'primary', + duplicateMode: 'continueduplicate', + duplicateFields: ['nickname'], + duplicateCollection: 'users', + iconColor: '#1677FF', + danger: false, + }, + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': + 'actionSettings:duplicate', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Duplicate") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Duplicate")}}', + 'x-component': + 'Tabs.TabPane', + 'x-designer': + 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': + 'popup:addNew:addBlock', + properties: { + dx47nd4vr1y: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Row', + 'x-app-version': + '1.3.32-beta', + properties: { + k0xrnil5pew: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.32-beta', + properties: { + l0bj7opq4ts: { + 'x-uid': + 'irw5rilaksj', + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-settings': + 'blockSettings:markdown', + 'x-decorator': + 'CardItem', + 'x-decorator-props': + { + name: 'markdown', + engine: + 'handlebars', + }, + 'x-component': + 'Markdown.Void', + 'x-editable': + false, + 'x-component-props': + { + content: + 'Duplicate markdown 2.', + }, + 'x-app-version': + '1.3.32-beta', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '989e6tagpg7', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'wbgxmynn5mb', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'q4tpvvow9ev', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5nyyobmuuyl', + 'x-async': false, + 'x-index': 1, + }, + k0dihwjrh0j: { + 'x-uid': '1m5bg14aiv9', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: 'new tab in drawer', + 'x-component': + 'Tabs.TabPane', + 'x-designer': + 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': + '1.3.32-beta', + properties: { + grid: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': + 'popup:common:addBlock', + 'x-app-version': + '1.3.32-beta', + properties: { + '84pz8mhlfi8': { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Row', + 'x-app-version': + '1.3.32-beta', + properties: { + dbm9wbvxfj5: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.32-beta', + properties: { + '5yhn7kfi2yw': + { + 'x-uid': + 'aa23f01rm3r', + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-settings': + 'blockSettings:markdown', + 'x-decorator': + 'CardItem', + 'x-decorator-props': + { + name: 'markdown', + engine: + 'handlebars', + }, + 'x-component': + 'Markdown.Void', + 'x-editable': + false, + 'x-component-props': + { + content: + 'new tab in drawer markdown 2.', + }, + 'x-app-version': + '1.3.32-beta', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'x2a3ivqab91', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'ekszy9o6ta0', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8yjfgyrvord', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'w4k13f0dqpt', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7vaia1fkno1', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'bvc3y7y3fem', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '4xso516rjk9', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'dyyuo828wdo', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'um86bfrnd0n', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'bwkvo3771d1', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8ecnnc4yvgz', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '6pmwqi2etjn', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'e88jnt5tv4e', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '2u9lxm7qivt', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kqabk7m6d7h', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'eqyqzt5letc', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7t4bewrinmr', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ouboqexioby', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'e363l9dbg2g', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '9p4tvagqpfd', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'a68euip3d3s', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hklfkdijm58', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '536m19repzv', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/plugins/@nocobase/plugin-action-export/README.md b/packages/plugins/@nocobase/plugin-action-export/README.md index 4540842319..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-action-export/README.md +++ b/packages/plugins/@nocobase/plugin-action-export/README.md @@ -1,9 +1,30 @@ -# export +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-export/README.zh-CN.md b/packages/plugins/@nocobase/plugin-action-export/README.zh-CN.md deleted file mode 100644 index 3e5e641b2a..0000000000 --- a/packages/plugins/@nocobase/plugin-action-export/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# Export - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-action-export/package.json b/packages/plugins/@nocobase/plugin-action-export/package.json index 85e9d7e1fc..5f7cd13350 100644 --- a/packages/plugins/@nocobase/plugin-action-export/package.json +++ b/packages/plugins/@nocobase/plugin-action-export/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "操作:导出记录", "description": "Export filtered records to excel, you can configure which fields to export.", "description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-export", diff --git a/packages/plugins/@nocobase/plugin-action-export/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-action-export/src/locale/en-US.json index 19f7aaa58e..c2a5cd203e 100644 --- a/packages/plugins/@nocobase/plugin-action-export/src/locale/en-US.json +++ b/packages/plugins/@nocobase/plugin-action-export/src/locale/en-US.json @@ -1,4 +1,6 @@ { "Export warning": "You can export up to {{limit}} rows of data at a time, any excess will be ignored.", - "Start export": "Start export" + "Start export": "Start export", + "True": "True", + "False": "False" } diff --git a/packages/plugins/@nocobase/plugin-action-export/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-action-export/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-action-export/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-action-export/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-action-export/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-action-export/src/locale/zh-CN.json index 7fc1bd6309..d8f5bbda9e 100644 --- a/packages/plugins/@nocobase/plugin-action-export/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-action-export/src/locale/zh-CN.json @@ -1,5 +1,7 @@ { "Export warning": "每次最多导出 {{limit}} 行数据,超出的将被忽略。", "Start export": "开始导出", - "another export action is running, please try again later.": "另一导出任务正在运行,请稍后重试。" + "another export action is running, please try again later.": "另一导出任务正在运行,请稍后重试。", + "True": "是", + "False": "否" } diff --git a/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts index 707c31ebea..78eb6f3808 100644 --- a/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts +++ b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts @@ -31,6 +31,183 @@ describe('export to xlsx with preset', () => { await app.destroy(); }); + describe('export with date field', () => { + let Post; + + beforeEach(async () => { + Post = app.db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' }, + { + name: 'datetime', + type: 'datetime', + interface: 'datetime', + uiSchema: { + 'x-component-props': { picker: 'date', dateFormat: 'YYYY-MM-DD', gmt: false, showTime: false, utc: true }, + type: 'string', + 'x-component': 'DatePicker', + title: 'dateTz', + }, + }, + { + name: 'dateOnly', + type: 'dateOnly', + interface: 'date', + defaultToCurrentTime: false, + onUpdateToCurrentTime: false, + timezone: true, + }, + { + name: 'datetimeNoTz', + type: 'datetimeNoTz', + interface: 'datetimeNoTz', + uiSchema: { + 'x-component-props': { picker: 'date', dateFormat: 'YYYY-MM-DD', gmt: false, showTime: false, utc: true }, + type: 'string', + 'x-component': 'DatePicker', + title: 'dateTz', + }, + }, + { + name: 'unixTimestamp', + type: 'unixTimestamp', + interface: 'unixTimestamp', + uiSchema: { + 'x-component-props': { + picker: 'date', + dateFormat: 'YYYY-MM-DD', + showTime: true, + timeFormat: 'HH:mm:ss', + }, + }, + }, + ], + }); + + await app.db.sync(); + }); + + it('should export with datetime field', async () => { + await Post.repository.create({ + values: { + title: 'p1', + datetime: '2024-05-10T01:42:35.000Z', + dateOnly: '2024-05-10', + datetimeNoTz: '2024-01-01 00:00:00', + unixTimestamp: '2024-05-10T01:42:35.000Z', + }, + }); + + const exporter = new XlsxExporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: Post, + chunkSize: 10, + columns: [ + { dataIndex: ['title'], defaultTitle: 'Title' }, + { + dataIndex: ['datetime'], + defaultTitle: 'datetime', + }, + { + dataIndex: ['dateOnly'], + defaultTitle: 'dateOnly', + }, + { + dataIndex: ['datetimeNoTz'], + defaultTitle: 'datetimeNoTz', + }, + { + dataIndex: ['unixTimestamp'], + defaultTitle: 'unixTimestamp', + }, + ], + }); + + const wb = await exporter.run(); + + const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`); + + try { + XLSX.writeFile(wb, xlsxFilePath); + + // read xlsx file + const workbook = XLSX.readFile(xlsxFilePath); + const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; + const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); + + const firstUser = sheetData[1]; + expect(firstUser[1]).toEqual('2024-05-10'); + expect(firstUser[2]).toEqual('2024-05-10'); + expect(firstUser[3]).toEqual('2024-01-01'); + expect(firstUser[4]).toEqual('2024-05-10 01:42:35'); + } finally { + fs.unlinkSync(xlsxFilePath); + } + }); + }); + + it('should export with checkbox field', async () => { + const Post = app.db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' }, + { + type: 'boolean', + name: 'test_field', + interface: 'checkbox', + uiSchema: { + type: 'boolean', + 'x-component': 'Checkbox', + }, + }, + ], + }); + + await app.db.sync(); + + await Post.repository.create({ + values: { + title: 'p1', + test_field: true, + }, + }); + + const exporter = new XlsxExporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: Post, + chunkSize: 10, + columns: [ + { dataIndex: ['title'], defaultTitle: 'Title' }, + { + dataIndex: ['test_field'], + defaultTitle: 'test_field', + }, + ], + }); + + const wb = await exporter.run(); + + const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`); + + try { + XLSX.writeFile(wb, xlsxFilePath); + + // read xlsx file + const workbook = XLSX.readFile(xlsxFilePath); + const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; + const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); + + const header = sheetData[0]; + expect(header).toEqual(['Title', 'test_field']); + + const data = sheetData[1]; + expect(data[1]).toBe('True'); + } finally { + fs.unlinkSync(xlsxFilePath); + } + }); + it('should export number field with cell format', async () => { const Post = app.db.collection({ name: 'posts', @@ -459,7 +636,7 @@ describe('export to xlsx', () => { title: 'test_date', }, name: 'test_date', - type: 'date', + type: 'datetime', interface: 'datetime', }, ], @@ -487,11 +664,7 @@ describe('export to xlsx', () => { ], }); - const wb = await exporter.run({ - get() { - return '+08:00'; - }, - }); + const wb = await exporter.run(); const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`); try { @@ -503,7 +676,7 @@ describe('export to xlsx', () => { const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); const firstUser = sheetData[1]; - expect(firstUser).toEqual(['some_title', '2024-05-10 09:42:35']); + expect(firstUser).toEqual(['some_title', '2024-05-10 01:42:35']); } finally { fs.unlinkSync(xlsxFilePath); } diff --git a/packages/plugins/@nocobase/plugin-action-import/README.md b/packages/plugins/@nocobase/plugin-action-import/README.md index 858ebdfe56..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-action-import/README.md +++ b/packages/plugins/@nocobase/plugin-action-import/README.md @@ -1,222 +1,30 @@ -# import +# NocoBase -English | [中文](./README.zh-CN.md) + -Excel 数据导入插件。 -## 安装激活 +## What is NocoBase -内置插件无需手动安装激活。 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 导入说明 +Homepage: +https://www.nocobase.com/ -### 数字类型字段 +Online Demo: +https://demo.nocobase.com/new -支持数字和百分比,`N/A` 或 `-` 的文案会被过滤掉 +Documents: +https://docs.nocobase.com/ -| 数字1 | 百分比 | 数字2 | 数字3 | -| -- | -- | -- | -- | -| 123 | 25% | N/A | - | +Commericial license & plugins: +https://www.nocobase.com/en/commercial -转 JSON 之后为 +License agreement: +https://www.nocobase.com/en/agreement -```ts -{ - "数字1": 123, - "百分比": 0.25, - "数字2": null, - "数字3": null, -} -``` -### 布尔类型字段 - -输入文案支持(英文不区分大小写): - -- `Yes` `Y` `True` `1` `是` -- `No` `N` `False` `0` `否` - -| 字段1 | 字段2 | 字段3 | 字段4 | 字段4 | -| -- | -- | -- | -- | -- | -| 否 | 是 | Y | true | 0 | - -转 JSON 之后为 - -```ts -{ - "字段1": false, - "字段2": true, - "字段3": true, - "字段4": true, - "字段5": false, -} -``` - -### 日期类型字段 - -| DateOnly | Local(+08:00) | GMT | -| -- | -- | -- | -| 2023-01-18 22:22:22 | 2023-01-18 22:22:22 | 2023-01-18 22:22:22 | - -转 JSON 之后为 - -```ts -{ - "DateOnly": "2023-01-18T00:00:00.000Z", - "Local(+08:00)": "2023-01-18T14:22:22.000Z", - "GMT": "2023-01-18T22:22:22.000Z", -} -``` - -### 选择类型字段 - -选项值和选项标签都可作为导入文案,多个选项之间以以逗号(`,` `,`)或顿号(`、`)区分 - -如字段 `优先级` 的可选项包括: - -| 选项值 | 选项标签 | -| -- | -- | -| low | 低 | -| medium | 中 | -| high | 低 | - -选项值和选项标签都可作为导入文案 - -| 优先级 | -| -- | -| 高 | -| low | - -转 JSON 之后为 - -```ts -[ - { "优先级": "high" }, - { "优先级": "low" }, -] -``` - -### 中国行政区字段 - -| 地区1 | 地区2 | -| -- | -- | -| 北京市/市辖区 | 天津市/市辖区 | - -转 JSON 之后为 - -```ts -{ - "地区1": ["11","1101"], - "地区2": ["12","1201"] -} -``` - -### 附件字段 - -| 附件 | -| --| -| https://www.nocobase.com/images/logo.png | - -转 JSON 之后为 - -```ts -{ - "附件": [ - { - "filename": "logo.png", - "title": "logo.png", - "extname": ".png", - "url": "https://www.nocobase.com/images/logo.png" - } - ] -} -``` - -### 关系类型字段 - -多条数据以逗号(`,` `,`)或顿号(`、`)区分 - -| 部门/名称 | 分类/标题 | -| -- | -- | -| 开发组 | 分类1、分类2 | - -转 JSON 之后为 - -```ts -{ - "部门": [1], // 1 为部门名称为「开发组」的记录 ID - "分类": [1,2], // 1,2 为分类标题为「分类1」和「分类2」的记录 ID -} -``` - -### JSON 类型字段 - -| JSON1 | -| -- | -| {"key":"value"} | - -转 JSON 之后为 - -```ts -{ - "JSON": {"key":"value"} -} -``` - -### 地图几何图形类型 - -| Point | Line | Polygon | Circle | -| -- | -- | -- | -- | -| 1,2 | (1,2),(3,4) | (1,2),(3,4),(1,2) | 1,2,3 | - -转 JSON 之后为 - -```ts -{ - "Point": [1,2], - "Line": [[1,2], [3,4]], - "Polygon": [[1,2], [3,4], [1,2]], - "Circle": [1,2,3] -} -``` - -## 自定义导入格式 - -通过 `db.registerFieldValueParsers()` 方法注册自定义的 `ValueParser`,如: - -```ts -import { BaseValueParser } from '@nocobase/database'; - -class PointValueParser extends BaseValueParser { - async setValue(value) { - if (Array.isArray(value)) { - this.value = value; - } else if (typeof value === 'string') { - this.value = value.split(','); - } else { - this.errors.push('Value invalid'); - } - } -} - -const db = new Database(); - -// type=point 的字段导入时,将通过 PointValueParser 解析数据 -db.registerFieldValueParsers({ - point: PointValueParser, -}); -``` - -导入示例 - -| Point | -| --| -| 1,2 | - -转 JSON 之后为 - -```ts -{ - "Point": [1,2] -} -``` +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-import/README.zh-CN.md b/packages/plugins/@nocobase/plugin-action-import/README.zh-CN.md deleted file mode 100644 index 68c6546c77..0000000000 --- a/packages/plugins/@nocobase/plugin-action-import/README.zh-CN.md +++ /dev/null @@ -1,222 +0,0 @@ -# import - -[English](./README.md) | 中文 - -Excel 数据导入插件。 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 导入说明 - -### 数字类型字段 - -支持数字和百分比,`N/A` 或 `-` 的文案会被过滤掉 - -| 数字1 | 百分比 | 数字2 | 数字3 | -| -- | -- | -- | -- | -| 123 | 25% | N/A | - | - -转 JSON 之后为 - -```ts -{ - "数字1": 123, - "百分比": 0.25, - "数字2": null, - "数字3": null, -} -``` - -### 布尔类型字段 - -输入文案支持(英文不区分大小写): - -- `Yes` `Y` `True` `1` `是` -- `No` `N` `False` `0` `否` - -| 字段1 | 字段2 | 字段3 | 字段4 | 字段4 | -| -- | -- | -- | -- | -- | -| 否 | 是 | Y | true | 0 | - -转 JSON 之后为 - -```ts -{ - "字段1": false, - "字段2": true, - "字段3": true, - "字段4": true, - "字段5": false, -} -``` - -### 日期类型字段 - -| DateOnly | Local(+08:00) | GMT | -| -- | -- | -- | -| 2023-01-18 22:22:22 | 2023-01-18 22:22:22 | 2023-01-18 22:22:22 | - -转 JSON 之后为 - -```ts -{ - "DateOnly": "2023-01-18T00:00:00.000Z", - "Local(+08:00)": "2023-01-18T14:22:22.000Z", - "GMT": "2023-01-18T22:22:22.000Z", -} -``` - -### 选择类型字段 - -选项值和选项标签都可作为导入文案,多个选项之间以以逗号(`,` `,`)或顿号(`、`)区分 - -如字段 `优先级` 的可选项包括: - -| 选项值 | 选项标签 | -| -- | -- | -| low | 低 | -| medium | 中 | -| high | 低 | - -选项值和选项标签都可作为导入文案 - -| 优先级 | -| -- | -| 高 | -| low | - -转 JSON 之后为 - -```ts -[ - { "优先级": "high" }, - { "优先级": "low" }, -] -``` - -### 中国行政区字段 - -| 地区1 | 地区2 | -| -- | -- | -| 北京市/市辖区 | 天津市/市辖区 | - -转 JSON 之后为 - -```ts -{ - "地区1": ["11","1101"], - "地区2": ["12","1201"] -} -``` - -### 附件字段 - -| 附件 | -| --| -| https://www.nocobase.com/images/logo.png | - -转 JSON 之后为 - -```ts -{ - "附件": [ - { - "filename": "logo.png", - "title": "logo.png", - "extname": ".png", - "url": "https://www.nocobase.com/images/logo.png" - } - ] -} -``` - -### 关系类型字段 - -多条数据以逗号(`,` `,`)或顿号(`、`)区分 - -| 部门/名称 | 分类/标题 | -| -- | -- | -| 开发组 | 分类1、分类2 | - -转 JSON 之后为 - -```ts -{ - "部门": [1], // 1 为部门名称为「开发组」的记录 ID - "分类": [1,2], // 1,2 为分类标题为「分类1」和「分类2」的记录 ID -} -``` - -### JSON 类型字段 - -| JSON1 | -| -- | -| {"key":"value"} | - -转 JSON 之后为 - -```ts -{ - "JSON": {"key":"value"} -} -``` - -### 地图几何图形类型 - -| Point | Line | Polygon | Circle | -| -- | -- | -- | -- | -| 1,2 | (1,2),(3,4) | (1,2),(3,4),(1,2) | 1,2,3 | - -转 JSON 之后为 - -```ts -{ - "Point": [1,2], - "Line": [[1,2], [3,4]], - "Polygon": [[1,2], [3,4], [1,2]], - "Circle": [1,2,3] -} -``` - -## 自定义导入格式 - -通过 `db.registerFieldValueParsers()` 方法注册自定义的 `ValueParser`,如: - -```ts -import { BaseValueParser } from '@nocobase/database'; - -class PointValueParser extends BaseValueParser { - async setValue(value) { - if (Array.isArray(value)) { - this.value = value; - } else if (typeof value === 'string') { - this.value = value.split(','); - } else { - this.errors.push('Value invalid'); - } - } -} - -const db = new Database(); - -// type=point 的字段导入时,将通过 PointValueParser 解析数据 -db.registerFieldValueParsers({ - point: PointValueParser, -}); -``` - -导入示例 - -| Point | -| --| -| 1,2 | - -转 JSON 之后为 - -```ts -{ - "Point": [1,2] -} -``` diff --git a/packages/plugins/@nocobase/plugin-action-import/package.json b/packages/plugins/@nocobase/plugin-action-import/package.json index 31fa642e5e..2a207b5646 100644 --- a/packages/plugins/@nocobase/plugin-action-import/package.json +++ b/packages/plugins/@nocobase/plugin-action-import/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "操作:导入记录", "description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.", "description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-import", diff --git a/packages/plugins/@nocobase/plugin-action-import/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-action-import/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-action-import/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-action-import/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-action-import/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-action-import/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-action-import/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-action-import/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts index 130a5f8ee1..9a4569b868 100644 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts @@ -38,24 +38,145 @@ describe('xlsx importer', () => { name: 'name', }, { - type: 'date', - name: 'date', + type: 'datetime', + name: 'datetime', interface: 'datetime', }, + { + type: 'datetimeNoTz', + name: 'datetimeNoTz', + interface: 'datetimeNoTz', + uiSchema: { + 'x-component-props': { + picker: 'date', + dateFormat: 'YYYY-MM-DD', + showTime: true, + timeFormat: 'HH:mm:ss', + }, + }, + }, + { + type: 'dateOnly', + name: 'dateOnly', + interface: 'date', + }, + { + type: 'unixTimestamp', + name: 'unixTimestamp', + interface: 'unixTimestamp', + uiSchema: { + 'x-component-props': { + picker: 'date', + dateFormat: 'YYYY-MM-DD', + showTime: true, + timeFormat: 'HH:mm:ss', + }, + }, + }, ], }); await app.db.sync(); }); - it('should import with date', async () => { + it('should import with dateOnly', async () => { const columns = [ { dataIndex: ['name'], defaultTitle: '姓名', }, { - dataIndex: ['date'], + dataIndex: ['dateOnly'], + defaultTitle: '日期', + }, + ]; + + const templateCreator = new TemplateCreator({ + collection: User, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa( + worksheet, + [ + ['test', 77383], + ['test2', '2021-10-18'], + ], + { origin: 'A2' }, + ); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns, + workbook: template, + }); + + await importer.run(); + + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(users[0]['dateOnly']).toBe('2111-11-12'); + expect(users[1]['dateOnly']).toBe('2021-10-18'); + }); + + it.skipIf(process.env['DB_DIALECT'] === 'sqlite')('should import with datetimeNoTz', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['datetimeNoTz'], + defaultTitle: '日期', + }, + ]; + + const templateCreator = new TemplateCreator({ + collection: User, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa( + worksheet, + [ + ['test', 77383], + ['test2', '2021-10-18 12:31:20'], + ['test3', 41557.4377314815], + ], + { origin: 'A2' }, + ); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns, + workbook: template, + }); + + await importer.run(); + + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(users[0]['datetimeNoTz']).toBe('2111-11-12 00:00:00'); + expect(users[1]['datetimeNoTz']).toBe('2021-10-18 12:31:20'); + expect(users[2]['datetimeNoTz']).toBe('2013-10-10 10:30:20'); + }); + + it('should import with unixTimestamp', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['unixTimestamp'], defaultTitle: '日期', }, ]; @@ -80,11 +201,44 @@ describe('xlsx importer', () => { await importer.run(); - expect(await User.repository.count()).toBe(1); + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(moment(users[0]['unixTimestamp']).toISOString()).toEqual('2111-11-12T00:00:00.000Z'); + }); - const user = await User.repository.findOne(); + it('should import with datetimeTz', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['datetime'], + defaultTitle: '日期', + }, + ]; - expect(moment(user.get('date')).format('YYYY-MM-DD')).toBe('2111-11-12'); + const templateCreator = new TemplateCreator({ + collection: User, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa(worksheet, [['test', 77383]], { origin: 'A2' }); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns, + workbook: template, + }); + + await importer.run(); + + const users = (await User.repository.find()).map((user) => user.toJSON()); + expect(moment(users[0]['datetime']).toISOString()).toEqual('2111-11-12T00:00:00.000Z'); }); }); @@ -281,9 +435,156 @@ describe('xlsx importer', () => { }); }); + describe('import with belongs to association', async () => { + let Profile; + let User; + + beforeEach(async () => { + Profile = app.db.collection({ + name: 'profiles', + autoGenId: false, + fields: [ + { + type: 'bigInt', + name: 'id', + primaryKey: true, + autoIncrement: true, + }, + { + type: 'string', + name: 'name', + }, + { + type: 'string', + name: 'userName', + }, + { + type: 'belongsTo', + name: 'user', + target: 'users', + foreignKey: 'userName', + targetKey: 'name', + }, + ], + }); + + User = app.db.collection({ + name: 'users', + autoGenId: false, + fields: [ + { + type: 'bigInt', + name: 'id', + primaryKey: true, + autoIncrement: true, + }, + { + type: 'string', + name: 'name', + unique: true, + }, + ], + }); + + await app.db.sync(); + + const user = await User.repository.create({ + values: { + name: 'User1', + }, + }); + }); + + it('should import with foreignKey', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '名称', + }, + { + dataIndex: ['userName'], + defaultTitle: '用户名', + }, + ]; + + const templateCreator = new TemplateCreator({ + collection: Profile, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa(worksheet, [['test', 'User1']], { + origin: 'A2', + }); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: Profile, + columns, + workbook: template, + }); + + await importer.run(); + + const profile = await Profile.repository.findOne({ + appends: ['user'], + }); + + expect(profile.get('user').get('name')).toBe('User1'); + expect(profile.get('name')).toBe('test'); + }); + + it('should import with association field', async () => { + const columns = [ + { + dataIndex: ['name'], + defaultTitle: '名称', + }, + { + dataIndex: ['user', 'name'], + defaultTitle: '用户名', + }, + ]; + + const templateCreator = new TemplateCreator({ + collection: Profile, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa(worksheet, [['test', 'User1']], { + origin: 'A2', + }); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: Profile, + columns, + workbook: template, + }); + + await importer.run(); + + const profile = await Profile.repository.findOne({ + appends: ['user'], + }); + + expect(profile.get('user').get('name')).toBe('User1'); + expect(profile.get('name')).toBe('test'); + }); + }); + describe('import with associations', () => { let User; let Post; + let Tag; + beforeEach(async () => { User = app.db.collection({ name: 'users', @@ -315,12 +616,98 @@ describe('xlsx importer', () => { target: 'users', interface: 'm2o', }, + { + type: 'belongsToMany', + name: 'tags', + target: 'tags', + interface: 'm2m', + through: 'postsTags', + }, + ], + }); + + Tag = app.db.collection({ + name: 'tags', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'belongsToMany', + name: 'posts', + target: 'posts', + interface: 'm2m', + through: 'postsTags', + }, ], }); await app.db.sync(); }); + it('should import many to many with id', async () => { + await Tag.repository.create({ + values: [ + { + title: 't1', + }, + { + title: 't2', + }, + ], + }); + + const columns = [ + { + dataIndex: ['title'], + defaultTitle: '名称', + }, + { + dataIndex: ['tags', 'id'], + defaultTitle: 'IDS', + }, + ]; + + const templateCreator = new TemplateCreator({ + collection: Post, + columns, + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa( + worksheet, + [ + ['test', '1,2'], + ['test2', 1], + ], + { + origin: 'A2', + }, + ); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: Post, + columns, + workbook: template, + }); + + await importer.run(); + + const posts = await Post.repository.find({ + appends: ['tags'], + }); + + expect(posts.length).toBe(2); + + expect(posts[0]['tags'].map((item: any) => item.id)).toEqual([1, 2]); + expect(posts[1]['tags'].map((item: any) => item.id)).toEqual([1]); + }); + it('should validate to many association', async () => { const columns = [ { diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/index.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/index.ts index 27425c4256..9613e9e6f2 100644 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/index.ts +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/index.ts @@ -8,16 +8,11 @@ */ import { Plugin } from '@nocobase/server'; -import { namespace } from '..'; import { downloadXlsxTemplate, importXlsx } from './actions'; -import { enUS, zhCN } from './locale'; import { importMiddleware } from './middleware'; export class PluginActionImportServer extends Plugin { beforeLoad() { - this.app.i18n.addResources('zh-CN', namespace, zhCN); - this.app.i18n.addResources('en-US', namespace, enUS); - this.app.on('afterInstall', async () => { if (!this.app.db.getRepository('roles')) { return; diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/en-US.json b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/en-US.json new file mode 100644 index 0000000000..ae75b93381 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/en-US.json @@ -0,0 +1,10 @@ +{ + "Yes": "Yes", + "No": "No", + "can not find value": "can not find value", + "password is empty": "password is empty", + "Incorrect time format": "Incorrect time format", + "Incorrect date format": "Incorrect date format", + "Incorrect email format": "Incorrect email format", + "Illegal percentage format": "Illegal percentage format" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/en-US.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/en-US.ts deleted file mode 100644 index c3cd2a5beb..0000000000 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/en-US.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Yes: 'Yes', - No: 'No', - 'can not find value': 'can not find value', - 'password is empty': 'password is empty', - 'Incorrect time format': 'Incorrect time format', - 'Incorrect date format': 'Incorrect date format', - 'Incorrect email format': 'Incorrect email format', - 'Illegal percentage format': 'Illegal percentage format', -}; diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/fr-FR.json b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/fr-FR.json new file mode 100644 index 0000000000..9b4ac2228d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/fr-FR.json @@ -0,0 +1,10 @@ +{ + "Yes": "Oui", + "No": "Non", + "can not find value": "valeur introuvable", + "password is empty": "le mot de passe est vide", + "Incorrect time format": "Format de l'heure incorrect", + "Incorrect date format": "Format de la date incorrect", + "Incorrect email format": "Format d'email incorrect", + "Illegal percentage format": "Format de pourcentage illégal" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/fr-FR.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/fr-FR.ts deleted file mode 100644 index 80c5ba0010..0000000000 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/fr-FR.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Yes: 'Oui', - No: 'Non', - 'can not find value': 'valeur introuvable', - 'password is empty': 'le mot de passe est vide', - 'Incorrect time format': 'Format de l\'heure incorrect', - 'Incorrect date format': 'Format de la date incorrect', - 'Incorrect email format': 'Format d\'email incorrect', - 'Illegal percentage format': 'Format de pourcentage illégal', -}; diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/zh-CN.json new file mode 100644 index 0000000000..17c8c08a8f --- /dev/null +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/zh-CN.json @@ -0,0 +1,11 @@ +{ + "Yes": "是", + "No": "否", + "can not find value": "找不到对应值", + "password is empty": "密码为空", + "Incorrect time format": "时间格式不正确", + "Incorrect date format": "日期格式不正确", + "Incorrect email format": "邮箱格式不正确", + "Illegal percentage format": "百分比格式有误", + "Imported template does not match, please download again.": "导入模板不匹配,请检查导入文件标题行或重新下载导入模板" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/locale/zh-CN.ts deleted file mode 100644 index a449fa6c6c..0000000000 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/locale/zh-CN.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Yes: '是', - No: '否', - 'can not find value': '找不到对应值', - 'password is empty': '密码为空', - 'Incorrect time format': '时间格式不正确', - 'Incorrect date format': '日期格式不正确', - 'Incorrect email format': '邮箱格式不正确', - 'Illegal percentage format': '百分比格式有误', - 'Imported template does not match, please download again.': '导入模板不匹配,请检查导入文件标题行或重新下载导入模板', -}; diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts index be60ea8df9..d1ea59d950 100644 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts @@ -172,6 +172,7 @@ export class XlsxImporter extends EventEmitter { }; if (column.dataIndex.length > 1) { + ctx.associationField = field; ctx.targetCollection = (field as IRelationField).targetCollection(); ctx.filterKey = column.dataIndex[1]; } diff --git a/packages/plugins/@nocobase/plugin-action-print/README.md b/packages/plugins/@nocobase/plugin-action-print/README.md index f41b598820..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-action-print/README.md +++ b/packages/plugins/@nocobase/plugin-action-print/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-action-print +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-action-print/package.json b/packages/plugins/@nocobase/plugin-action-print/package.json index cf6fff5038..2009403fd9 100644 --- a/packages/plugins/@nocobase/plugin-action-print/package.json +++ b/packages/plugins/@nocobase/plugin-action-print/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-print", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-print", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print", diff --git a/packages/plugins/@nocobase/plugin-api-doc/README.md b/packages/plugins/@nocobase/plugin-api-doc/README.md index 79befb22e2..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-api-doc/README.md +++ b/packages/plugins/@nocobase/plugin-api-doc/README.md @@ -1,80 +1,30 @@ -# api-doc +# NocoBase -English | [中文](./README.zh-CN.md) + -## Introduction +## What is NocoBase -This plugin is based on `swagger` to write documentation. +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## How to access the documentation +Homepage: +https://www.nocobase.com/ -1. The access address in the plugin center is `{domain}/admin/settings/api-doc/documentation` -2. The access address outside the plugin center is `{domain}/api-documentation` +Online Demo: +https://demo.nocobase.com/new -## How to write swagger documentation +Documents: +https://docs.nocobase.com/ -> The method in the plugin is the same +Commericial license & plugins: +https://www.nocobase.com/en/commercial -1. `src/swagger.{ts,json}` -2. `src/swagger/index.{ts,json}` +License agreement: +https://www.nocobase.com/en/agreement -The file paths above can all be traversed to write documentation. Just export your written documentation by default. An example is shown below: -```ts -export default { - info: { - title: 'NocoBase API - Api-doc plugin', - }, - tags: [], - paths: {}, - components: { - schemas: {} - } -}; -``` - -Usually, you only need to write **info.title**, **tags**, **paths**, and **components**. Other information such as **server** and **info** are merged into our **base-swagger**. - -Base swagger includes the following code: - -```ts -// base swagger -export default { - openapi: '3.0.3', - info: { - title: 'NocoBase API documentation', - description: '', - contact: { - url: 'https://github.com/nocobase/nocobase/issues', - }, - license: { - name: 'Core packages are Apache 2.0 & Plugins packages are AGPL 3.0 licensed.', - url: 'https://github.com/nocobase/nocobase#license', - }, - }, - externalDocs: { - description: 'Find out more about NocoBase', - url: 'https://docs.nocobase.com/', - }, - components: { - securitySchemes: { - 'api-key': { - type: 'http', - scheme: 'bearer', - }, - }, - }, - security: [ - { - 'api-key': [], - }, - ], -}; -``` - -> Note that configurations that can only be obtained at runtime, such as the server and version fields, are not filled in the base-swagger. - -You can also override these defaults. When writing the swagger documentation for your plugin, you should consider whether your plugin's documentation can be accessed independently. - -For detailed swagger writing rules, please refer to the [official documentation](https://swagger.io/docs/specification/about/). +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-api-doc/README.zh-CN.md b/packages/plugins/@nocobase/plugin-api-doc/README.zh-CN.md deleted file mode 100644 index 4388ad4b88..0000000000 --- a/packages/plugins/@nocobase/plugin-api-doc/README.zh-CN.md +++ /dev/null @@ -1,79 +0,0 @@ -# api-doc - -[English](./README.md) | 中文 - -## 简介 - -该插件基于 `swagger` 编写文档。 - -## 如何访问文档 - -1. 在插件中心访问地址是 `{domain}/admin/settings/api-doc/documentation` -2. 在插件中心外访问地址是 `{domain}/api-documentation` - -## 如何编写 swagger 文档 - -> 插件内方式一样 - -1. `src/swagger.{ts,json}` -2. `src/swagger/index.{ts,json}` - -上面的文件路径均可遍写文档,只需最后将您编写的文档默认导出即可,例子如下: - -```ts -export default { - info: { - title: 'NocoBase API - Auth plugin', - }, - tags: [], - paths: {}, - components: { - schemas: {} - } -}; -``` - -通常你只需要编写 **info.title**, **tags**, **paths**, **components** 即可其他内容如 **server**, **info** 的其他信息都合并我们的 **base-swagger**. - -base swagger 包含如下代码 -```ts -// base swagger -export default { - openapi: '3.0.3', - info: { - title: 'NocoBase API documentation', - description: '', - contact: { - url: 'https://github.com/nocobase/nocobase/issues', - }, - license: { - name: 'Core packages are Apache 2.0 & Plugins packages are AGPL 3.0 licensed.', - url: 'https://github.com/nocobase/nocobase#license', - }, - }, - externalDocs: { - description: 'Find out more about NocoBase', - url: 'https://docs.nocobase.com/', - }, - components: { - securitySchemes: { - 'api-key': { - type: 'http', - scheme: 'bearer', - }, - }, - }, - security: [ - { - 'api-key': [], - }, - ], -}; - -``` - -> 注意:涉及到运行时才能获取的配置,并没有填写在 base-swagger 里,如 server, version 字段 - -这些默认的配置你同样也可以覆盖它,在你编写插件的 `swagger` 文档时,你需要视你的插件文档是独立的文档,可以被单独访问。 - -详细的 `swagger` 编写规则请参考[官方文档](https://swagger.io/docs/specification/about/) diff --git a/packages/plugins/@nocobase/plugin-api-doc/package.json b/packages/plugins/@nocobase/plugin-api-doc/package.json index 900fdcc674..c36a4f4c77 100644 --- a/packages/plugins/@nocobase/plugin-api-doc/package.json +++ b/packages/plugins/@nocobase/plugin-api-doc/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-api-doc", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "API documentation", "displayName.zh-CN": "API 文档", "description": "An OpenAPI documentation generator for NocoBase HTTP API.", diff --git a/packages/plugins/@nocobase/plugin-api-doc/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-api-doc/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-api-doc/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-api-doc/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-api-doc/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-api-doc/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-api-doc/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-api-doc/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-api-keys/README.md b/packages/plugins/@nocobase/plugin-api-keys/README.md index 80897d2803..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/README.md +++ b/packages/plugins/@nocobase/plugin-api-keys/README.md @@ -1,9 +1,30 @@ -# api-keys +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-api-keys/README.zh-CN.md b/packages/plugins/@nocobase/plugin-api-keys/README.zh-CN.md deleted file mode 100644 index 9089fa1944..0000000000 --- a/packages/plugins/@nocobase/plugin-api-keys/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# api-keys - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-api-keys/package.json b/packages/plugins/@nocobase/plugin-api-keys/package.json index 9562263bab..3bd51ab3ee 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/package.json +++ b/packages/plugins/@nocobase/plugin-api-keys/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "认证:API 密钥", "description": "Allows users to use API key to access application's HTTP API", "description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/api-keys", diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx b/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx index cd7b22ea71..37c63e8d41 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx @@ -8,12 +8,11 @@ */ import { Plugin } from '@nocobase/client'; -import { NAMESPACE } from '../constants'; import { Configuration } from './Configuration'; export class PluginAPIKeysClient extends Plugin { async load() { - this.pluginSettingsManager.add(NAMESPACE, { + this.pluginSettingsManager.add('api-keys', { icon: 'KeyOutlined', title: this.t('API keys'), Component: Configuration, diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-api-keys/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-api-keys/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-api-keys/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-api-keys/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-api-keys/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-api-keys/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/en-US.json b/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/en-US.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/en-US.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/fr-FR.json b/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/fr-FR.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/fr-FR.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/zh-CN.json new file mode 100644 index 0000000000..a1d38fcbc0 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/zh-CN.json @@ -0,0 +1,3 @@ +{ + "Role not found": "角色不存在" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-audit-logs/README.md b/packages/plugins/@nocobase/plugin-audit-logs/README.md index e617cc5b1a..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-audit-logs/README.md +++ b/packages/plugins/@nocobase/plugin-audit-logs/README.md @@ -1,11 +1,30 @@ -# audit-logs +# NocoBase -English | [中文](./README.zh-CN.md) + -审计日志插件。 -## 安装激活 +## What is NocoBase -内置插件无需手动安装激活。 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 使用方法 +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-audit-logs/README.zh-CN.md b/packages/plugins/@nocobase/plugin-audit-logs/README.zh-CN.md deleted file mode 100644 index 8dc8105860..0000000000 --- a/packages/plugins/@nocobase/plugin-audit-logs/README.zh-CN.md +++ /dev/null @@ -1,11 +0,0 @@ -# audit-logs - -[English](./README.md) | 中文 - -审计日志插件。 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-audit-logs/package.json b/packages/plugins/@nocobase/plugin-audit-logs/package.json index 6412b18151..64d97f627b 100644 --- a/packages/plugins/@nocobase/plugin-audit-logs/package.json +++ b/packages/plugins/@nocobase/plugin-audit-logs/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-audit-logs", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "Audit logs (deprecated)", "displayName.zh-CN": "审计日志(废弃)", "description": "This plugin is deprecated. There will be a new audit log plugin in the future.", diff --git a/packages/plugins/@nocobase/plugin-audit-logs/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-audit-logs/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-audit-logs/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-audit-logs/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-auth-sms/README.md b/packages/plugins/@nocobase/plugin-auth-sms/README.md index 16e8a088de..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-auth-sms/README.md +++ b/packages/plugins/@nocobase/plugin-auth-sms/README.md @@ -1,23 +1,30 @@ -# SMS Auth +# NocoBase -提供短信登录认证功能。 + -## 依赖 -- `@nocobase/auth` 认证插件,提供认证相关功能,表、模型、函数复用等。 -- `@nocobase/plugin-verification` 验证码插件,提供短信发送功能。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -### 新增验证码Provider -插件设置 - Verification/验证码 -新增一个Provider, 并设置为默认方式。 +Homepage: +https://www.nocobase.com/ - +Online Demo: +https://demo.nocobase.com/new -### 新增SMS认证器 -插件设置 - Authentication/认证 -新增,选择认证类型为SMS,新增一个Authenticator。 +Documents: +https://docs.nocobase.com/ -效果如图: - +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth-sms/package.json b/packages/plugins/@nocobase/plugin-auth-sms/package.json index cd90bfead4..9d7e290f9e 100644 --- a/packages/plugins/@nocobase/plugin-auth-sms/package.json +++ b/packages/plugins/@nocobase/plugin-auth-sms/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "认证:短信", "description": "SMS authentication.", "description.zh-CN": "通过短信验证码认证身份。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/auth-sms", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth-sms", diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-auth-sms/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-auth-sms/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-auth-sms/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-auth-sms/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-auth-sms/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-auth-sms/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/en-US.json b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/en-US.json new file mode 100644 index 0000000000..c538049f66 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/en-US.json @@ -0,0 +1,8 @@ +{ + "The email is incorrect, please re-enter": "The email is incorrect, please re-enter", + "Please fill in your email address": "Please fill in your email address", + "The password is incorrect, please re-enter": "The password is incorrect, please re-enter", + "Not a valid cellphone number, please re-enter": "Not a valid cellphone number, please re-enter", + "The phone number has been registered, please login directly": "The phone number has been registered, please login directly", + "The phone number is not registered, please register first": "The phone number is not registered, please register first" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/en-US.ts b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/en-US.ts deleted file mode 100644 index a5780ac09c..0000000000 --- a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/en-US.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'The email is incorrect, please re-enter', - 'Please fill in your email address': 'Please fill in your email address', - 'The password is incorrect, please re-enter': 'The password is incorrect, please re-enter', - 'Not a valid cellphone number, please re-enter': 'Not a valid cellphone number, please re-enter', - 'The phone number has been registered, please login directly': - 'The phone number has been registered, please login directly', - 'The phone number is not registered, please register first': - 'The phone number is not registered, please register first', -}; diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/fr-FR.json b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/fr-FR.json new file mode 100644 index 0000000000..fe1d556755 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/fr-FR.json @@ -0,0 +1,8 @@ +{ + "The email is incorrect, please re-enter": "L'email est incorrect, veuillez le saisir à nouveau", + "Please fill in your email address": "Veuillez remplir votre adresse e-mail", + "The password is incorrect, please re-enter": "Le mot de passe est incorrect, veuillez le saisir à nouveau", + "Not a valid cellphone number, please re-enter": "Numéro de téléphone portable invalide, veuillez le saisir à nouveau", + "The phone number has been registered, please login directly": "Le numéro de téléphone a été enregistré, veuillez vous connecter directement", + "The phone number is not registered, please register first": "Le numéro de téléphone n'est pas enregistré, veuillez vous inscrire d'abord" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/fr-FR.ts b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/fr-FR.ts deleted file mode 100644 index 314562e3bd..0000000000 --- a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/fr-FR.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'L\'email est incorrect, veuillez le saisir à nouveau', - 'Please fill in your email address': 'Veuillez remplir votre adresse e-mail', - 'The password is incorrect, please re-enter': 'Le mot de passe est incorrect, veuillez le saisir à nouveau', - 'Not a valid cellphone number, please re-enter': 'Numéro de téléphone portable invalide, veuillez le saisir à nouveau', - 'The phone number has been registered, please login directly': - 'Le numéro de téléphone a été enregistré, veuillez vous connecter directement', - 'The phone number is not registered, please register first': - 'Le numéro de téléphone n\'est pas enregistré, veuillez vous inscrire d\'abord', -}; diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/index.ts b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/index.ts deleted file mode 100644 index 29e1be6c80..0000000000 --- a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export { default as enUS } from './en-US'; -export { default as zhCN } from './zh-CN'; -export { default as ptBR } from './pt-BR'; diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/ja-JP.json new file mode 100644 index 0000000000..edb4ffefc6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/ja-JP.json @@ -0,0 +1,4 @@ +{ + "Please fill in your email address": "メールアドレスを入力してください", + "The password is incorrect, please re-enter": "パスワードが正しくありません。再度入力してください。" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/ja-JP.ts b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/ja-JP.ts deleted file mode 100644 index 63406d55ce..0000000000 --- a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/ja-JP.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'Please fill in your email address': 'メールアドレスを入力してください', - 'The password is incorrect, please re-enter': 'パスワードが正しくありません。再度入力してください。', -}; diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/pt-BR.json b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/pt-BR.json new file mode 100644 index 0000000000..0c7c87f6e1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/pt-BR.json @@ -0,0 +1,8 @@ +{ + "The email is incorrect, please re-enter": "O e-mail está incorreto, por favor, digite novamente", + "Please fill in your email address": "Por favor, preencha o seu endereço de e-mail", + "The password is incorrect, please re-enter": "A senha está incorreta, por favor, digite novamente", + "Not a valid cellphone number, please re-enter": "Número de celular inválido, por favor, digite novamente", + "The phone number has been registered, please login directly": "O número de celular já está registrado, por favor, faça login diretamente", + "The phone number is not registered, please register first": "O número de celular não está registrado, por favor, registre-se primeiro" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/pt-BR.ts b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/pt-BR.ts deleted file mode 100644 index 6da802d81d..0000000000 --- a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/pt-BR.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'O e-mail está incorreto, por favor, digite novamente', - 'Please fill in your email address': 'Por favor, preencha o seu endereço de e-mail', - 'The password is incorrect, please re-enter': 'A senha está incorreta, por favor, digite novamente', - 'Not a valid cellphone number, please re-enter': 'Número de celular inválido, por favor, digite novamente', - 'The phone number has been registered, please login directly': - 'O número de celular já está registrado, por favor, faça login diretamente', - 'The phone number is not registered, please register first': - 'O número de celular não está registrado, por favor, registre-se primeiro', -}; diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/zh-CN.json new file mode 100644 index 0000000000..6a280adbc4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/zh-CN.json @@ -0,0 +1,9 @@ +{ + "The email is incorrect, please re-enter": "邮箱有误,请重新输入", + "Please fill in your email address": "请填写邮箱", + "The password is incorrect, please re-enter": "密码有误,请重新输入", + "Not a valid cellphone number, please re-enter": "不是有效的手机号,请重新输入", + "The phone number has been registered, please login directly": "手机号已注册,请直接登录", + "The phone number is not registered, please register first": "手机号未注册,请先注册", + "Please keep and enable at least one authenticator": "请至少保留并启用一个认证器" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/zh-CN.ts deleted file mode 100644 index 98fe263b57..0000000000 --- a/packages/plugins/@nocobase/plugin-auth-sms/src/server/locale/zh-CN.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': '邮箱有误,请重新输入', - 'Please fill in your email address': '请填写邮箱', - 'The password is incorrect, please re-enter': '密码有误,请重新输入', - 'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入', - 'The phone number has been registered, please login directly': '手机号已注册,请直接登录', - 'The phone number is not registered, please register first': '手机号未注册,请先注册', - 'Please keep and enable at least one authenticator': '请至少保留并启用一个认证器', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/README.md b/packages/plugins/@nocobase/plugin-auth/README.md index 7fb8535a6a..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-auth/README.md +++ b/packages/plugins/@nocobase/plugin-auth/README.md @@ -1,117 +1,30 @@ -# Auth +# NocoBase -提供基础认证功能和扩展认证器管理功能。 - -## 使用方法 - -### 认证器管理 -页面:系统设置 - 认证 + - +## What is NocoBase -#### 内置认证器 -- 名称:basic -- 认证类型:邮箱密码登录 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! - +Homepage: +https://www.nocobase.com/ - +Online Demo: +https://demo.nocobase.com/new -#### 增加认证器 -Add new - 选择认证类型 +Documents: +https://docs.nocobase.com/ - +Commericial license & plugins: +https://www.nocobase.com/en/commercial -#### 启用/禁用 -Actions - Edit - 勾选/取消Enabled +License agreement: +https://www.nocobase.com/en/agreement - -#### 配置认证器 -Actions - Edit - -## 开发一个登录插件 -### 接口 -Nocobase内核提供了扩展登录方式的接入和管理。扩展登录插件的核心逻辑处理,需要继承内核的`Auth`类,并对相应的标准接口进行实现。 -参考`core/auth/auth.ts` - -```TypeScript -import { Auth } from '@nocobase/auth'; - -class CustomAuth extends Auth { - set user(user) {} - get user() {} - - async check() {} - async signIn() {} -} -``` - -多数情况下,扩展的用户登录方式也将沿用现有的jwt逻辑来生成用户访问API的凭证,插件也可以继承`BaseAuth`类以便复用部分逻辑代码,如`check`, `signIn`接口。 - -```TypeScript -import { BaseAuth } from '@nocobase/auth'; - -class CustomAuth extends BaseAuth { - constructor(config: AuthConfig) { - const userCollection = config.ctx.db.getCollection('users'); - super({ ...config, userCollection }); - } - - async validate() {} -} -``` - -### 用户数据 - -`@nocobase/plugin-auth`插件提供了`usersAuthenticators`表来建立users和authenticators,即用户和认证方式的关联。 -通常情况下,扩展登录方式用`users`和`usersAuthenticators`来存储相应的用户数据即可,特殊情况下才需要自己新增Collection. -`users`存储的是最基础的用户数据,邮箱、昵称和密码。 -`usersAuthenticators`存储扩展登录方式数据 -- uuid: 该种认证方式的用户唯一标识,如手机号、微信openid等 -- meta: JSON字段,其他需要保存的信息 -- userId: 用户id -- authenticator:认证器名字 - -对于用户操作,`Authenticator`模型也提供了几个封装的方法,可以在`CustomAuth`类中通过`this.authenticator[方法名]`使用: -- `findUser(uuid: string): UserModel` - 查询用户 -- `newUser(uuid: string, values?: any): UserModel` - 创建新用户 -- `findOrCreateUser(uuid: string, userValues?: any): UserModel` - 查找或创建新用户 - -### 注册 -扩展的登录方式需要向内核注册。 -```TypeScript -async load() { - this.app.authManager.registerTypes('custom-auth-type', { - auth: CustomAuth, - }); -} -``` - -### 客户端API -#### OptionsComponentProvider -可供用户配置的认证器配置项 -- authType 认证方式 -- component 配置组件 -```TypeScript - -``` - -`Options`组件使用的值是`authenticator`的`options`字段,如果有需要暴露在前端的配置,应该放在`options.public`字段中。`authenticators:publicList`接口会返回`options.public`字段的值。 - -#### SigninPageProvider -自定义登录页界面 -- authType 认证方式 -- tabTitle 登录页tab标题 -- component 登录页组件 - -#### SignupPageProvider -自定义注册页界面 -- authType 认证方式 -- component 注册页组件 - -#### SigninPageExtensionProvider -自定义登录页下方的扩展内容 -- authType 认证方式 -- component 扩展组件 +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth/package.json b/packages/plugins/@nocobase/plugin-auth/package.json index 3a35767cc7..8222052171 100644 --- a/packages/plugins/@nocobase/plugin-auth/package.json +++ b/packages/plugins/@nocobase/plugin-auth/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-auth", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/auth", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth", diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/__e2e__/auth.test.ts b/packages/plugins/@nocobase/plugin-auth/src/client/__e2e__/auth.test.ts index 7744cc818f..6c756c2729 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/__e2e__/auth.test.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/client/__e2e__/auth.test.ts @@ -19,10 +19,13 @@ test.describe('auth', () => { }); test('register', async ({ page }) => { + // Generate a random username + const username = `zidonghuaceshi${Math.random().toString(36).substring(2, 15)}`; + await page.goto('/'); await page.getByRole('link', { name: 'Create an account' }).click(); await page.getByPlaceholder('Username').click(); - await page.getByPlaceholder('Username').fill('zidonghuaceshi'); + await page.getByPlaceholder('Username').fill(username); await page.getByPlaceholder('Password', { exact: true }).click(); await page.getByPlaceholder('Password', { exact: true }).fill('zidonghuaceshi123'); await page.getByPlaceholder('Confirm password').click(); @@ -31,14 +34,14 @@ test.describe('auth', () => { await expect(page.getByText('Sign up successfully, and automatically jump to the sign in page')).toBeVisible(); - // 用新账户登录 + // Sign in with the new account await page.getByPlaceholder('Username/Email').click(); - await page.getByPlaceholder('Username/Email').fill('zidonghuaceshi'); + await page.getByPlaceholder('Username/Email').fill(username); await page.getByPlaceholder('Password').click(); await page.getByPlaceholder('Password').fill('zidonghuaceshi123'); await page.getByRole('button', { name: 'Sign in' }).click(); await page.getByTestId('user-center-button').hover(); - await expect(page.getByText('zidonghuaceshi')).toBeVisible(); + await expect(page.getByText(username)).toBeVisible(); }); }); diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx index 6f531bed04..13fb2fe30e 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/basic/Options.tsx @@ -9,7 +9,8 @@ import { SchemaComponent } from '@nocobase/client'; import React from 'react'; -import { useAuthTranslation } from '../locale'; +import { lang, useAuthTranslation } from '../locale'; +import { FormTab, ArrayTable } from '@formily/antd-v5'; import { Alert } from 'antd'; export const Options = () => { @@ -17,30 +18,137 @@ export const Options = () => { return ( { + const field = value?.filter((item) => item.show && item.required); + if (!field?.length) { + return t('At least one field is required'); + } +} }}`, + default: [ + { + field: 'username', + show: true, + required: true, + }, + { + field: 'email', + show: false, + required: false, + }, + ], + items: { + type: 'object', + 'x-decorator': 'ArrayItems.Item', + properties: { + column0: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { width: 20, align: 'center' }, + properties: { + sort: { + type: 'void', + 'x-component': 'ArrayTable.SortHandle', + }, + }, + }, + column1: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { width: 100, title: lang('Field') }, + properties: { + field: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Select', + enum: [ + { + label: lang('Username'), + value: 'username', + }, + { + label: lang('Email'), + value: 'email', + }, + ], + 'x-read-pretty': true, + }, + }, + }, + column2: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { width: 80, title: lang('Show') }, + properties: { + show: { + type: 'boolean', + 'x-decorator': 'FormItem', + 'x-component': 'Checkbox', + }, + }, + }, + column3: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { width: 80, title: lang('Required') }, + properties: { + required: { + type: 'boolean', + 'x-decorator': 'FormItem', + 'x-component': 'Checkbox', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, }, }} /> diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignUpForm.tsx b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignUpForm.tsx index fd7da1a4b6..010c99e1f3 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignUpForm.tsx +++ b/packages/plugins/@nocobase/plugin-auth/src/client/basic/SignUpForm.tsx @@ -9,7 +9,7 @@ import { SchemaComponent } from '@nocobase/client'; import { ISchema } from '@formily/react'; -import React from 'react'; +import React, { useMemo } from 'react'; import { uid } from '@formily/shared'; import { useAuthTranslation } from '../locale'; import { useAPIClient } from '@nocobase/client'; @@ -26,6 +26,23 @@ export interface UseSignupProps { }; } +const schemas = { + username: { + type: 'string', + 'x-component': 'Input', + 'x-validator': { username: true }, + 'x-decorator': 'FormItem', + 'x-component-props': { placeholder: '{{t("Username")}}', style: {} }, + }, + email: { + type: 'string', + 'x-component': 'Input', + 'x-validator': 'email', + 'x-decorator': 'FormItem', + 'x-component-props': { placeholder: '{{t("Email")}}', style: {} }, + }, +}; + export const useSignUp = (props?: UseSignupProps) => { const navigate = useNavigate(); const form = useForm(); @@ -43,19 +60,12 @@ export const useSignUp = (props?: UseSignupProps) => { }; }; -const signupPageSchema: ISchema = { +const getSignupPageSchema = (fieldSchemas: any): ISchema => ({ type: 'object', name: uid(), 'x-component': 'FormV2', properties: { - username: { - type: 'string', - required: true, - 'x-component': 'Input', - 'x-validator': { username: true }, - 'x-decorator': 'FormItem', - 'x-component-props': { placeholder: '{{t("Username")}}', style: {} }, - }, + ...fieldSchemas, password: { type: 'string', required: true, @@ -121,7 +131,7 @@ const signupPageSchema: ISchema = { }, }, }, -}; +}); export const SignUpForm = ({ authenticatorName: name }: { authenticatorName: string }) => { const { t } = useAuthTranslation(); @@ -130,8 +140,27 @@ export const SignUpForm = ({ authenticatorName: name }: { authenticatorName: str }; const authenticator = useAuthenticator(name); const { options } = authenticator; + let { signupForm } = options; + if (!(signupForm?.length && signupForm.some((item: any) => item.show && item.required))) { + signupForm = [{ field: 'username', show: true, required: true }]; + } + const fieldSchemas = useMemo(() => { + return signupForm + .filter((field: { show: boolean }) => field.show) + .reduce((prev: any, item: { field: string; required: boolean }) => { + const field = item.field; + if (schemas[field]) { + prev[field] = schemas[field]; + if (item.required) { + prev[field].required = true; + } + return prev; + } + }, {}); + }, [signupForm]); if (!options?.allowSignUp) { return ; } - return ; + const schema = getSignupPageSchema(fieldSchemas); + return ; }; diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/locale/index.ts b/packages/plugins/@nocobase/plugin-auth/src/client/locale/index.ts index 57ba7189a7..90ef09e12e 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/locale/index.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/client/locale/index.ts @@ -8,9 +8,14 @@ */ import { useTranslation } from 'react-i18next'; +import { i18n } from '@nocobase/client'; export const NAMESPACE = 'auth'; export function useAuthTranslation() { return useTranslation([NAMESPACE, 'client'], { nsMode: 'fallback' }); } + +export function lang(key: string) { + return i18n.t(key, { ns: [NAMESPACE, 'client'], nsMode: 'fallback' }); +} diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json index f6f77a628c..a9335f2252 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json +++ b/packages/plugins/@nocobase/plugin-auth/src/locale/en-US.json @@ -23,5 +23,10 @@ "No authentication methods available.": "No authentication methods available.", "The password is inconsistent, please re-enter": "The password is inconsistent, please re-enter", "Sign-in": "Sign-in", - "Password": "Password" + "Password": "Password", + "The username/email or password is incorrect, please re-enter": "The username/email or password is incorrect, please re-enter", + "Show": "Show", + "Sign up settings": "Sign up settings", + "Sign up form": "Sign up form", + "At least one field is required": "At least one field is required" } diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-auth/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-auth/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-auth/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-auth/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-auth/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-auth/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json index 1e244da8a7..599a9307cd 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-auth/src/locale/zh-CN.json @@ -23,5 +23,10 @@ "No authentication methods available.": "没有可用的认证方式。", "The password is inconsistent, please re-enter": "密码不一致,请重新输入", "Sign-in": "登录", - "Password": "密码" + "Password": "密码", + "The username/email or password is incorrect, please re-enter": "用户名/邮箱或密码有误,请重新输入", + "Show": "显示", + "Sign up settings": "注册设置", + "Sign up form": "注册表单", + "At least one field is required": "至少需要设置一个必填字段" } diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts index 1a7e9ea4b6..1ec00affdf 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/__tests__/actions.test.ts @@ -117,7 +117,7 @@ describe('actions', () => { email: 'no-exists@nocobase.com', }); expect(res.statusCode).toEqual(401); - expect(res.error.text).toBe('The username or email is incorrect, please re-enter'); + expect(res.error.text).toBe('The username/email or password is incorrect, please re-enter'); }); it('should check password when signing in', async () => { @@ -126,7 +126,7 @@ describe('actions', () => { password: 'incorrect', }); expect(res.statusCode).toEqual(401); - expect(res.error.text).toBe('The password is incorrect, please re-enter'); + expect(res.error.text).toBe('The username/email or password is incorrect, please re-enter'); }); it('should sign in with password', async () => { @@ -260,6 +260,11 @@ describe('actions', () => { }); it('should check username when signing up', async () => { + const res1 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ + username: '', + }); + expect(res1.statusCode).toEqual(400); + expect(res1.error.text).toBe('Please enter a valid username'); const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ username: '@@', }); @@ -267,6 +272,39 @@ describe('actions', () => { expect(res.error.text).toBe('Please enter a valid username'); }); + it('should check email when signing up', async () => { + const repo = db.getRepository('authenticators'); + await repo.update({ + filter: { + name: 'basic', + }, + values: { + options: { + public: { + allowSignUp: true, + signupForm: [{ field: 'email', show: true, required: true }], + }, + }, + }, + }); + const res1 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ + email: '', + }); + expect(res1.statusCode).toEqual(400); + expect(res1.error.text).toBe('Please enter a valid email address'); + const res2 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ + email: 'abc', + }); + expect(res2.statusCode).toEqual(400); + expect(res2.error.text).toBe('Please enter a valid email address'); + const res3 = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ + email: 'test1@nocobase.com', + password: '123', + confirm_password: '123', + }); + expect(res3.statusCode).toEqual(200); + }); + it('should check password when signing up', async () => { const res = await agent.post('/auth:signUp').set({ 'X-Authenticator': 'basic' }).send({ username: 'new', diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/actions/auth.ts b/packages/plugins/@nocobase/plugin-auth/src/server/actions/auth.ts index 81ca2c90e2..71f18907db 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/actions/auth.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/actions/auth.ts @@ -9,22 +9,52 @@ /* istanbul ignore file -- @preserve */ import { Context, Next } from '@nocobase/actions'; +import { PasswordField } from '@nocobase/database'; +import { namespace } from '../../preset'; export default { - lostPassword: async (ctx: Context, next: Next) => { - ctx.body = await ctx.auth.lostPassword(); - await next(); - }, - resetPassword: async (ctx: Context, next: Next) => { - ctx.body = await ctx.auth.resetPassword(); - await next(); - }, - getUserByResetToken: async (ctx: Context, next: Next) => { - ctx.body = await ctx.auth.getUserByResetToken(); - await next(); - }, + // lostPassword: async (ctx: Context, next: Next) => { + // ctx.body = await ctx.auth.lostPassword(); + // await next(); + // }, + // resetPassword: async (ctx: Context, next: Next) => { + // ctx.body = await ctx.auth.resetPassword(); + // await next(); + // }, + // getUserByResetToken: async (ctx: Context, next: Next) => { + // ctx.body = await ctx.auth.getUserByResetToken(); + // await next(); + // }, changePassword: async (ctx: Context, next: Next) => { - ctx.body = await ctx.auth.changePassword(); + const { + values: { oldPassword, newPassword, confirmPassword }, + } = ctx.action.params; + if (newPassword !== confirmPassword) { + ctx.throw(400, ctx.t('The password is inconsistent, please re-enter', { ns: namespace })); + } + const currentUser = ctx.auth.user; + if (!currentUser) { + ctx.throw(401); + } + let key: string; + if (currentUser.username) { + key = 'username'; + } else { + key = 'email'; + } + const user = await ctx.db.getRepository('users').findOne({ + where: { + [key]: currentUser[key], + }, + }); + const pwd = ctx.db.getCollection('users').getField('password'); + const isValid = await pwd.verify(oldPassword, user.password); + if (!isValid) { + ctx.throw(401, ctx.t('The password is incorrect, please re-enter', { ns: namespace })); + } + user.password = newPassword; + await user.save(); + ctx.body = currentUser; await next(); }, }; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts b/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts index c77b76ba0a..0c3185487e 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/server/basic-auth.ts @@ -39,17 +39,41 @@ export class BasicAuth extends BaseAuth { }); if (!user) { - ctx.throw(401, ctx.t('The username or email is incorrect, please re-enter', { ns: namespace })); + ctx.throw(401, ctx.t('The username/email or password is incorrect, please re-enter', { ns: namespace })); } const field = this.userCollection.getField('password'); const valid = await field.verify(password, user.password); if (!valid) { - ctx.throw(401, ctx.t('The password is incorrect, please re-enter', { ns: namespace })); + ctx.throw(401, ctx.t('The username/email or password is incorrect, please re-enter', { ns: namespace })); } return user; } + private verfiySignupParams(values: any) { + const options = this.authenticator.options?.public || {}; + let { signupForm } = options; + if (!(signupForm?.length && signupForm.some((item: any) => item.show && item.required))) { + signupForm = [{ field: 'username', show: true, required: true }]; + } + const { username, email } = values; + const usernameSetting = signupForm.find((item: any) => item.field === 'username'); + if (usernameSetting && usernameSetting.show) { + if ((username && !this.validateUsername(username)) || (usernameSetting.required && !username)) { + throw new Error('Please enter a valid username'); + } + } + const emailSetting = signupForm.find((item: any) => item.field === 'email'); + if (emailSetting && emailSetting.show) { + if (email && !/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(email)) { + throw new Error('Please enter a valid email address'); + } + if (emailSetting.required && !email) { + throw new Error('Please enter a valid email address'); + } + } + } + async signUp() { const ctx = this.ctx; const options = this.authenticator.options?.public || {}; @@ -58,9 +82,11 @@ export class BasicAuth extends BaseAuth { } const User = ctx.db.getRepository('users'); const { values } = ctx.action.params; - const { username, password, confirm_password } = values; - if (!this.validateUsername(username)) { - ctx.throw(400, ctx.t('Please enter a valid username', { ns: namespace })); + const { username, email, password, confirm_password } = values; + try { + this.verfiySignupParams(values); + } catch (error) { + ctx.throw(400, this.ctx.t(error.message, { ns: namespace })); } if (!password) { ctx.throw(400, ctx.t('Please enter a password', { ns: namespace })); @@ -68,7 +94,7 @@ export class BasicAuth extends BaseAuth { if (password !== confirm_password) { ctx.throw(400, ctx.t('The password is inconsistent, please re-enter', { ns: namespace })); } - const user = await User.create({ values: { username, password } }); + const user = await User.create({ values: { username, email, password } }); return user; } @@ -130,37 +156,4 @@ export class BasicAuth extends BaseAuth { } return user; } - - async changePassword() { - const ctx = this.ctx; - const { - values: { oldPassword, newPassword, confirmPassword }, - } = ctx.action.params; - if (newPassword !== confirmPassword) { - ctx.throw(400, ctx.t('The password is inconsistent, please re-enter', { ns: namespace })); - } - const currentUser = ctx.auth.user; - if (!currentUser) { - ctx.throw(401); - } - let key: string; - if (currentUser.username) { - key = 'username'; - } else { - key = 'email'; - } - const user = await this.userRepository.findOne({ - where: { - [key]: currentUser[key], - }, - }); - const pwd = this.userCollection.getField('password'); - const isValid = await pwd.verify(oldPassword, user.password); - if (!isValid) { - ctx.throw(401, ctx.t('The password is incorrect, please re-enter', { ns: namespace })); - } - user.password = newPassword; - await user.save(); - return currentUser; - } } diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.json b/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.json new file mode 100644 index 0000000000..c538049f66 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.json @@ -0,0 +1,8 @@ +{ + "The email is incorrect, please re-enter": "The email is incorrect, please re-enter", + "Please fill in your email address": "Please fill in your email address", + "The password is incorrect, please re-enter": "The password is incorrect, please re-enter", + "Not a valid cellphone number, please re-enter": "Not a valid cellphone number, please re-enter", + "The phone number has been registered, please login directly": "The phone number has been registered, please login directly", + "The phone number is not registered, please register first": "The phone number is not registered, please register first" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.ts deleted file mode 100644 index a5780ac09c..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/en-US.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'The email is incorrect, please re-enter', - 'Please fill in your email address': 'Please fill in your email address', - 'The password is incorrect, please re-enter': 'The password is incorrect, please re-enter', - 'Not a valid cellphone number, please re-enter': 'Not a valid cellphone number, please re-enter', - 'The phone number has been registered, please login directly': - 'The phone number has been registered, please login directly', - 'The phone number is not registered, please register first': - 'The phone number is not registered, please register first', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.json b/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.json new file mode 100644 index 0000000000..57ce346e36 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.json @@ -0,0 +1,8 @@ +{ + "The email is incorrect, please re-enter": "L'email est incorrect, veuillez le saisir à nouveau", + "Please fill in your email address": "Veuillez remplir votre adresse e-mail", + "The password is incorrect, please re-enter": "Le mot de passe est incorrect, veuillez le saisir à nouveau", + "Not a valid cellphone number, please re-enter": "Numéro de téléphone portable non valide, veuillez le saisir à nouveau", + "The phone number has been registered, please login directly": "Le numéro de téléphone a été enregistré, veuillez vous connecter directement", + "The phone number is not registered, please register first": "Le numéro de téléphone n'est pas enregistré, veuillez vous inscrire d'abord" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.ts deleted file mode 100644 index 58967f0cff..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/fr-FR.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'L\'email est incorrect, veuillez le saisir à nouveau', - 'Please fill in your email address': 'Veuillez remplir votre adresse e-mail', - 'The password is incorrect, please re-enter': 'Le mot de passe est incorrect, veuillez le saisir à nouveau', - 'Not a valid cellphone number, please re-enter': 'Numéro de téléphone portable non valide, veuillez le saisir à nouveau', - 'The phone number has been registered, please login directly': - 'Le numéro de téléphone a été enregistré, veuillez vous connecter directement', - 'The phone number is not registered, please register first': - 'Le numéro de téléphone n\'est pas enregistré, veuillez vous inscrire d\'abord', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/index.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/index.ts deleted file mode 100644 index 29e1be6c80..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export { default as enUS } from './en-US'; -export { default as zhCN } from './zh-CN'; -export { default as ptBR } from './pt-BR'; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.json new file mode 100644 index 0000000000..edb4ffefc6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.json @@ -0,0 +1,4 @@ +{ + "Please fill in your email address": "メールアドレスを入力してください", + "The password is incorrect, please re-enter": "パスワードが正しくありません。再度入力してください。" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.ts deleted file mode 100644 index 63406d55ce..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/ja-JP.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'Please fill in your email address': 'メールアドレスを入力してください', - 'The password is incorrect, please re-enter': 'パスワードが正しくありません。再度入力してください。', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.json b/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.json new file mode 100644 index 0000000000..0c7c87f6e1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.json @@ -0,0 +1,8 @@ +{ + "The email is incorrect, please re-enter": "O e-mail está incorreto, por favor, digite novamente", + "Please fill in your email address": "Por favor, preencha o seu endereço de e-mail", + "The password is incorrect, please re-enter": "A senha está incorreta, por favor, digite novamente", + "Not a valid cellphone number, please re-enter": "Número de celular inválido, por favor, digite novamente", + "The phone number has been registered, please login directly": "O número de celular já está registrado, por favor, faça login diretamente", + "The phone number is not registered, please register first": "O número de celular não está registrado, por favor, registre-se primeiro" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.ts deleted file mode 100644 index 6da802d81d..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/pt-BR.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The email is incorrect, please re-enter': 'O e-mail está incorreto, por favor, digite novamente', - 'Please fill in your email address': 'Por favor, preencha o seu endereço de e-mail', - 'The password is incorrect, please re-enter': 'A senha está incorreta, por favor, digite novamente', - 'Not a valid cellphone number, please re-enter': 'Número de celular inválido, por favor, digite novamente', - 'The phone number has been registered, please login directly': - 'O número de celular já está registrado, por favor, faça login diretamente', - 'The phone number is not registered, please register first': - 'O número de celular não está registrado, por favor, registre-se primeiro', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.json new file mode 100644 index 0000000000..7b23f33316 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.json @@ -0,0 +1,10 @@ +{ + "The username or email is incorrect, please re-enter": "用户名或邮箱有误,请重新输入", + "The password is incorrect, please re-enter": "密码有误,请重新输入", + "Not a valid cellphone number, please re-enter": "不是有效的手机号,请重新输入", + "The phone number has been registered, please login directly": "手机号已注册,请直接登录", + "The phone number is not registered, please register first": "手机号未注册,请先注册", + "Please keep and enable at least one authenticator": "请至少保留并启用一个认证器", + "Please enter your username or email": "请输入用户名或邮箱", + "Please enter a valid username": "请输入有效的用户名" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.ts deleted file mode 100644 index de99c16e07..0000000000 --- a/packages/plugins/@nocobase/plugin-auth/src/server/locale/zh-CN.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'The username or email is incorrect, please re-enter': '用户名或邮箱有误,请重新输入', - 'The password is incorrect, please re-enter': '密码有误,请重新输入', - 'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入', - 'The phone number has been registered, please login directly': '手机号已注册,请直接登录', - 'The phone number is not registered, please register first': '手机号未注册,请先注册', - 'Please keep and enable at least one authenticator': '请至少保留并启用一个认证器', - 'Please enter your username or email': '请输入用户名或邮箱', - 'Please enter a valid username': '请输入有效的用户名', -}; diff --git a/packages/plugins/@nocobase/plugin-auth/src/swagger/index.ts b/packages/plugins/@nocobase/plugin-auth/src/swagger/index.ts index 4b066083b1..52ffbf7ee2 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/swagger/index.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/swagger/index.ts @@ -168,165 +168,165 @@ export default { }, }, }, - '/auth:lostPassword': { - post: { - description: 'Lost password', - tags: ['Basic auth'], - security: [], - requestBody: { - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - email: { - type: 'string', - description: '邮箱', - }, - }, - }, - }, - }, - }, - responses: { - 200: { - description: 'successful operation', - content: { - 'application/json': { - schema: { - allOf: [ - { - $ref: '#/components/schemas/user', - }, - { - type: 'object', - properties: { - resetToken: { - type: 'string', - description: '重置密码的token', - }, - }, - }, - ], - }, - }, - }, - }, - 400: { - description: 'Please fill in your email address', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/error', - }, - }, - }, - }, - 401: { - description: 'The email is incorrect, please re-enter', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/error', - }, - }, - }, - }, - }, - }, - }, - '/auth:resetPassword': { - post: { - description: 'Reset password', - tags: ['Basic auth'], - security: [], - requestBody: { - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - email: { - type: 'string', - description: '邮箱', - }, - password: { - type: 'string', - description: '密码', - }, - resetToken: { - type: 'string', - description: '重置密码的token', - }, - }, - }, - }, - }, - }, - responses: { - 200: { - description: 'successful operation', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/user', - }, - }, - }, - }, - 404: { - description: 'User not found', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/error', - }, - }, - }, - }, - }, - }, - }, - '/auth:getUserByResetToken': { - get: { - description: 'Get user by reset token', - tags: ['Basic auth'], - security: [], - parameters: [ - { - name: 'token', - in: 'query', - description: '重置密码的token', - required: true, - schema: { - type: 'string', - }, - }, - ], - responses: { - 200: { - description: 'ok', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/user', - }, - }, - }, - }, - 401: { - description: 'Unauthorized', - content: { - 'application/json': { - schema: { - $ref: '#/components/schemas/error', - }, - }, - }, - }, - }, - }, - }, + // '/auth:lostPassword': { + // post: { + // description: 'Lost password', + // tags: ['Basic auth'], + // security: [], + // requestBody: { + // content: { + // 'application/json': { + // schema: { + // type: 'object', + // properties: { + // email: { + // type: 'string', + // description: '邮箱', + // }, + // }, + // }, + // }, + // }, + // }, + // responses: { + // 200: { + // description: 'successful operation', + // content: { + // 'application/json': { + // schema: { + // allOf: [ + // { + // $ref: '#/components/schemas/user', + // }, + // { + // type: 'object', + // properties: { + // resetToken: { + // type: 'string', + // description: '重置密码的token', + // }, + // }, + // }, + // ], + // }, + // }, + // }, + // }, + // 400: { + // description: 'Please fill in your email address', + // content: { + // 'application/json': { + // schema: { + // $ref: '#/components/schemas/error', + // }, + // }, + // }, + // }, + // 401: { + // description: 'The email is incorrect, please re-enter', + // content: { + // 'application/json': { + // schema: { + // $ref: '#/components/schemas/error', + // }, + // }, + // }, + // }, + // }, + // }, + // }, + // '/auth:resetPassword': { + // post: { + // description: 'Reset password', + // tags: ['Basic auth'], + // security: [], + // requestBody: { + // content: { + // 'application/json': { + // schema: { + // type: 'object', + // properties: { + // email: { + // type: 'string', + // description: '邮箱', + // }, + // password: { + // type: 'string', + // description: '密码', + // }, + // resetToken: { + // type: 'string', + // description: '重置密码的token', + // }, + // }, + // }, + // }, + // }, + // }, + // responses: { + // 200: { + // description: 'successful operation', + // content: { + // 'application/json': { + // schema: { + // $ref: '#/components/schemas/user', + // }, + // }, + // }, + // }, + // 404: { + // description: 'User not found', + // content: { + // 'application/json': { + // schema: { + // $ref: '#/components/schemas/error', + // }, + // }, + // }, + // }, + // }, + // }, + // }, + // '/auth:getUserByResetToken': { + // get: { + // description: 'Get user by reset token', + // tags: ['Basic auth'], + // security: [], + // parameters: [ + // { + // name: 'token', + // in: 'query', + // description: '重置密码的token', + // required: true, + // schema: { + // type: 'string', + // }, + // }, + // ], + // responses: { + // 200: { + // description: 'ok', + // content: { + // 'application/json': { + // schema: { + // $ref: '#/components/schemas/user', + // }, + // }, + // }, + // }, + // 401: { + // description: 'Unauthorized', + // content: { + // 'application/json': { + // schema: { + // $ref: '#/components/schemas/error', + // }, + // }, + // }, + // }, + // }, + // }, + // }, '/auth:changePassword': { post: { description: 'Change password', diff --git a/packages/plugins/@nocobase/plugin-backup-restore/README.md b/packages/plugins/@nocobase/plugin-backup-restore/README.md index 3ebd07f182..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/README.md +++ b/packages/plugins/@nocobase/plugin-backup-restore/README.md @@ -1,118 +1,30 @@ -# Duplicator +# NocoBase -English | [中文](./README.zh-CN.md) + -NocoBase 应用的备份与还原插件,可用于应用的复制、迁移、升级等场景。 -## 安装激活 +## What is NocoBase -内置插件无需手动安装激活。 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 使用方法 +Homepage: +https://www.nocobase.com/ -Duplicator 插件提供了 `dump` 和 `restore` 命令,分别用于备份和还原应用数据,可用于单应用的备份和还原,也可以跨应用。如果跨应用还原数据,请保证目标应用 NocoBase 版本与源应用一致,相对应插件也已下载本地。 +Online Demo: +https://demo.nocobase.com/new -**⚠️ 如果使用了继承(PostgreSQL)、视图、触发器等不兼容的特性,跨数据库还原备份数据可能失败。** +Documents: +https://docs.nocobase.com/ -### 备份数据 +Commericial license & plugins: +https://www.nocobase.com/en/commercial -```bash -yarn nocobase dump -``` +License agreement: +https://www.nocobase.com/en/agreement -选择需要备份的插件表结构及其数据 -```bash -? Select the plugin collections to be dumped (Press to select, to toggle all, to invert selection, and to proceed) - == Required == - - migration (core) (Disabled) - - collections (collection-manager) (Disabled) - - uiSchemas (ui-schema-storage) (Disabled) - - uiRoutes (ui-routes-storage) (Disabled) - - acl (acl) (Disabled) - - workflowConfig (workflow) (Disabled) - - snapshot-field (snapshot-field) (Disabled) - - sequences (sequence-field) (Disabled) - == Optional == -❯◉ executionLogs (workflow) - ◉ users (users) - ◉ storageSetting (file-manager) - ◉ attachmentRecords (file-manager) - ◉ systemSettings (system-settings) - ◉ verificationProviders (verification) - ◉ verificationData (verification) - ◉ oidcProviders (oidc) - ◉ samlProviders (saml) - ◉ mapConfiguration (map) -(Move up and down to reveal more choices) -``` - -选择需要备份的其他数据表的记录 - -```bash -? Select the collection records to be dumped (Press to select, to toggle all, to invert selection, and to proceed) -❯◉ Test1 -❯◉ Test2 -❯◉ Test3 -``` - -数据备份成功之后,备份文件位于 `storage/duplicator` 目录下: - -```bash -dumped to /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump -dumped file size: 20.8 kB -``` - -### 还原数据 - -```bash -yarn nocobase restore /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump -``` - -导入前请先备份数据 - -```bash -? Danger !!! This action will overwrite your current data, please make sure you have a backup❗️❗️ (y/N) -``` - -选择需要还原的插件表结构及其数据 - -```bash -? Select the plugin collections to be restored (Press to select, to toggle all, to invert selection, and to proceed) - == Required == - - migration (core) (Disabled) - - collections (collection-manager) (Disabled) - - uiSchemas (ui-schema-storage) (Disabled) - - uiRoutes (ui-routes-storage) (Disabled) - - acl (acl) (Disabled) - - workflowConfig (workflow) (Disabled) - - sequences (sequence-field) (Disabled) - == Optional == -❯◯ executionLogs (workflow) - ◯ users (users) - ◯ storageSetting (file-manager) - ◯ attachmentRecords (file-manager) - ◯ systemSettings (system-settings) - ◯ verificationProviders (verification) - ◯ verificationData (verification) - ◯ auditLogs (audit-logs) - ◯ iframe html storage (iframe-block) -``` - -选择需要还原的其他数据表的记录 - -```bash -? Select the collection records to be restored (Press to select, to toggle all, to invert selection, and to proceed) -❯◉ Test1 -❯◉ Test2 -❯◉ Test3 -``` - -成功之后,重启应用 - -```bash -# for development -yarn dev -# for production -yarn start -``` +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-backup-restore/README.zh-CN.md b/packages/plugins/@nocobase/plugin-backup-restore/README.zh-CN.md deleted file mode 100644 index 2deed9c0ff..0000000000 --- a/packages/plugins/@nocobase/plugin-backup-restore/README.zh-CN.md +++ /dev/null @@ -1,118 +0,0 @@ -# Duplicator - -[English](./README.md) | 中文 - -NocoBase 应用的备份与还原插件,可用于应用的复制、迁移、升级等场景。 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 - -Duplicator 插件提供了 `dump` 和 `restore` 命令,分别用于备份和还原应用数据,可用于单应用的备份和还原,也可以跨应用。如果跨应用还原数据,请保证目标应用 NocoBase 版本与源应用一致,相对应插件也已下载本地。 - -**⚠️ 如果使用了继承(PostgreSQL)、视图、触发器等不兼容的特性,跨数据库还原备份数据可能失败。** - -### 备份数据 - -```bash -yarn nocobase dump -``` - -选择需要备份的插件表结构及其数据 - -```bash -? Select the plugin collections to be dumped (Press to select, to toggle all, to invert selection, and to proceed) - == Required == - - migration (core) (Disabled) - - collections (collection-manager) (Disabled) - - uiSchemas (ui-schema-storage) (Disabled) - - uiRoutes (ui-routes-storage) (Disabled) - - acl (acl) (Disabled) - - workflowConfig (workflow) (Disabled) - - snapshot-field (snapshot-field) (Disabled) - - sequences (sequence-field) (Disabled) - == Optional == -❯◉ executionLogs (workflow) - ◉ users (users) - ◉ storageSetting (file-manager) - ◉ attachmentRecords (file-manager) - ◉ systemSettings (system-settings) - ◉ verificationProviders (verification) - ◉ verificationData (verification) - ◉ oidcProviders (oidc) - ◉ samlProviders (saml) - ◉ mapConfiguration (map) -(Move up and down to reveal more choices) -``` - -选择需要备份的其他数据表的记录 - -```bash -? Select the collection records to be dumped (Press to select, to toggle all, to invert selection, and to proceed) -❯◉ Test1 -❯◉ Test2 -❯◉ Test3 -``` - -数据备份成功之后,备份文件位于 `storage/duplicator` 目录下: - -```bash -dumped to /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump -dumped file size: 20.8 kB -``` - -### 还原数据 - -```bash -yarn nocobase restore /your/apps/a/storage/duplicator/dump-20230210T223910.nbdump -``` - -导入前请先备份数据 - -```bash -? Danger !!! This action will overwrite your current data, please make sure you have a backup❗️❗️ (y/N) -``` - -选择需要还原的插件表结构及其数据 - -```bash -? Select the plugin collections to be restored (Press to select, to toggle all, to invert selection, and to proceed) - == Required == - - migration (core) (Disabled) - - collections (collection-manager) (Disabled) - - uiSchemas (ui-schema-storage) (Disabled) - - uiRoutes (ui-routes-storage) (Disabled) - - acl (acl) (Disabled) - - workflowConfig (workflow) (Disabled) - - sequences (sequence-field) (Disabled) - == Optional == -❯◯ executionLogs (workflow) - ◯ users (users) - ◯ storageSetting (file-manager) - ◯ attachmentRecords (file-manager) - ◯ systemSettings (system-settings) - ◯ verificationProviders (verification) - ◯ verificationData (verification) - ◯ auditLogs (audit-logs) - ◯ iframe html storage (iframe-block) -``` - -选择需要还原的其他数据表的记录 - -```bash -? Select the collection records to be restored (Press to select, to toggle all, to invert selection, and to proceed) -❯◉ Test1 -❯◉ Test2 -❯◉ Test3 -``` - -成功之后,重启应用 - -```bash -# for development -yarn dev -# for production -yarn start -``` diff --git a/packages/plugins/@nocobase/plugin-backup-restore/package.json b/packages/plugins/@nocobase/plugin-backup-restore/package.json index e91355cc2f..172404d8a9 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/package.json +++ b/packages/plugins/@nocobase/plugin-backup-restore/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "应用的备份与还原(废弃)", "description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.", "description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/backup-restore", diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja-JP.json new file mode 100644 index 0000000000..a7bb019288 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja-JP.json @@ -0,0 +1,24 @@ +{ + "System metadata": "システムメタデータ", + "System config": "システム構成", + "Business data": "ビジネスデータ", + "Backup & Restore": "バックアップとリストア", + "Backup": "バックアップ", + "Restore": "リストア", + "Configuration": "構成", + "Select the data to be backed up": "バックアップされたデータの選択", + "Select the data to be restored": "リストアされたデータの選択", + "Learn more": "詳細", + "Start backup": "バックアップの開始", + "Start restore": "リストアを開始", + "Backed up successfully": "バックアップ成功", + "Plugin": "プラグイン", + "file uploaded successfully": "ファイルのアップロードに成功しました", + "Download": "ダウンロード", + "Restore backup from local": "ローカルからバックアップに返信", + "Backup instructions": "バックアップの説明", + "File size": "ファイルサイズ", + "New backup": "新規バックアップ", + "Origin": "ソース", + "Backing up": "バックアップ中" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja-JP.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja-JP.ts deleted file mode 100644 index 9d51baf961..0000000000 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja-JP.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'System metadata': 'システムメタデータ', - 'System config': 'システム構成', - 'Business data': 'ビジネスデータ', - 'Backup & Restore': 'バックアップとリストア', - Backup: 'バックアップ', - Restore: 'リストア', - Configuration: '構成', - 'Select the data to be backed up': 'バックアップされたデータの選択', - 'Select the data to be restored': 'リストアされたデータの選択', - 'Learn more': '詳細', - 'Start backup': 'バックアップの開始', - 'Start restore': 'リストアを開始', - 'Backed up successfully': 'バックアップ成功', - Plugin: 'プラグイン', - 'file uploaded successfully': 'ファイルのアップロードに成功しました', - Download: 'ダウンロード', - 'Restore backup from local':'ローカルからバックアップに返信', - 'Backup instructions':'バックアップの説明', - 'File size':'ファイルサイズ', - 'New backup':'新規バックアップ', - 'Origin':'ソース', - 'Backing up':'バックアップ中' -}; diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja_JP.json deleted file mode 100644 index 96acf7bdba..0000000000 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ja_JP.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "Backup file": "バックアップファイル", - "System metadata": "システムメタデータ", - "System config": "システム設定", - "Business data": "業務データ", - "Backup & Restore": "バックアップとリストア", - "Backup": "バックアップ", - "Restore": "リストア", - "Configuration": "設定", - "Select the data to be backed up": "バックアップするデータを選択", - "Select the data to be restored": "リストアするデータを選択", - "Click or drag file to this area to upload": "クリックまたはファイルをこの領域にドラッグしてアップロード", - "Learn more": "詳細を表示", - "Start backup": "バックアップを開始", - "Start restore": "リストアを開始", - "Backed up successfully": "バックアップが成功しました", - "Plugin": "プラグイン", - "file uploaded successfully": "ファイルが正常にアップロードされました", - "file upload failed": "ファイルのアップロードに失敗しました", - "Download": "ダウンロード", - "Restore backup from local": "ローカルからバックアップをリストア", - "Backup instructions": "バックアップ手順", - "File size": "ファイルサイズ", - "New backup": "新規バックアップ", - "Origin": "起源", - "Backing up": "バックアップ中", - "Refresh": "更新", - "Delete": "削除", - "Deleted successfully": "削除が成功しました", - "required.title": "必須データ", - "user.title": "ユーザーデータ", - "log.title": "ログデータ", - "custom.title": "カスタムコレクションデータ", - "skipped.title": "スキップされたデータ", - "unknown.title": "不明", - "third-party.title": "サードパーティサービス情報", - "required.description": "必要なデータ", - "user.description": "ユーザーデータ", - "log.description": "ログデータ", - "custom.description": "カスタムコレクションデータ", - "skipped.description": "スキップされたデータ", - "unknown.description": "ダンプルール未設定のデータ", - "third-party.description": "サードパーティサービス情報", - "Select Import data": "インポートデータを選択する", - "Select Import Plugins": "プラグインをインポートしてください", - "Select User Collections": "ユーザーデータを選択してください", - "Basic Data": "基本データ", - "Optional Data": "オプションデータ", - "User Data": "ユーザーデータ" -} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-backup-restore/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-backup-restore/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/pt-BR.json b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/pt-BR.json new file mode 100644 index 0000000000..cb3bd67645 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/pt-BR.json @@ -0,0 +1,25 @@ +{ + "System metadata": "Metadados do Sistema", + "System config": "configuração do sistema", + "Business data": "Dados comerciais", + "Backup & Restore": "Cópia de Segurança e Restauração", + "Backup": "cópias de segurança", + "Restore": "redução", + "Configuration": "atribuição", + "Select the data to be backed up": "Seleccionar os dados de cópia de segurança", + "Select the data to be restored": "Seleccionar os dados restaurados", + "Learn more": "Saiba mais", + "Start restore": "Iniciar a Restauração", + "Start backup": "Iniciar a cópia de segurança", + "Backed up successfully": "Cópia de segurança bem sucedida", + "Plugin": "Plugins", + "file uploaded successfully": "O ficheiro foi enviado com sucesso", + "file upload failed": "O envio do ficheiro falhou", + "Download": "download", + "Restore backup from local": "Restaurar a cópia de segurança localmente", + "Backup instructions": "Instruções de cópia de segurança", + "File size": "tamanho do ficheiro", + "New backup": "Nova Cópia de Segurança", + "Origin": "fonte", + "Backing up": "Cópia de segurança em curso" +} diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/pt-BR.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/locale/pt-BR.ts deleted file mode 100644 index 49a5afa2bb..0000000000 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/locale/pt-BR.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -const locale = { - 'System metadata': 'Metadados do Sistema', - 'System config': 'configuração do sistema', - 'Business data': 'Dados comerciais', - 'Backup & Restore': 'Cópia de Segurança e Restauração', - Backup: 'cópias de segurança', - Restore: 'redução', - Configuration: 'atribuição', - 'Select the data to be backed up': 'Seleccionar os dados de cópia de segurança', - 'Select the data to be restored': 'Seleccionar os dados restaurados', - 'Learn more': 'Saiba mais', - 'Start restore': 'Iniciar a Restauração', - 'Start backup': 'Iniciar a cópia de segurança', - 'Backed up successfully': 'Cópia de segurança bem sucedida', - 'Plugin':'Plugins', - 'file uploaded successfully': 'O ficheiro foi enviado com sucesso', - 'file upload failed': 'O envio do ficheiro falhou', - 'Download':'download', - 'Restore backup from local':'Restaurar a cópia de segurança localmente', - 'Backup instructions':'Instruções de cópia de segurança', - 'File size':'tamanho do ficheiro', - 'New backup':'Nova Cópia de Segurança', - 'Origin':'fonte', - 'Backing up':'Cópia de segurança em curso' -}; - -export default locale; diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts index 9e85ae37f8..2dc78f594f 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/__tests__/dumper.test.ts @@ -155,6 +155,45 @@ describe('dumper', () => { expect(items.length).toBe(1); }); + it('should dump and restore datetimeNoTz field', async () => { + await db.getRepository('collections').create({ + values: { + name: 'tests', + fields: [ + { + type: 'datetimeNoTz', + name: 'test_data', + }, + ], + }, + context: {}, + }); + + await db.getRepository('tests').create({ + values: { + test_data: '2013-10-10 12:11:00', + }, + }); + + const dumper = new Dumper(app); + + const result = await dumper.dump({ + groups: new Set(['required', 'custom']), + }); + + const restorer = new Restorer(app, { + backUpFilePath: result.filePath, + }); + + await restorer.restore({ + groups: new Set(['required', 'custom']), + }); + + const testCollection = app.db.getCollection('tests'); + const items = await testCollection.repository.find(); + expect(items.length).toBe(1); + }); + it('should dump and restore uuid field', async () => { await db.getRepository('collections').create({ values: { diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts index 793368711d..fcd8bfefd0 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/field-value-writer.ts @@ -7,10 +7,11 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { DataTypes, Field } from '@nocobase/database'; +import { Database, DataTypes, Field } from '@nocobase/database'; import lodash from 'lodash'; +import moment from 'moment/moment'; -type WriterFunc = (val: any) => any; +type WriterFunc = (val: any, database: Database) => any; const getMapFieldWriter = (field: Field) => { return (val) => { @@ -28,7 +29,7 @@ const getMapFieldWriter = (field: Field) => { export class FieldValueWriter { static writers = new Map(); - static write(field: Field, val) { + static write(field: Field, val, database) { if (val === null) return val; if (field.type == 'point' || field.type == 'lineString' || field.type == 'circle' || field.type === 'polygon') { @@ -36,10 +37,11 @@ export class FieldValueWriter { } const fieldType = field.typeToString(); + const writer = FieldValueWriter.writers[fieldType]; if (writer) { - val = writer(val); + val = writer(val, database); } return val; @@ -88,4 +90,21 @@ FieldValueWriter.registerWriter([DataTypes.JSON.toString(), DataTypes.JSONB.toSt } }); +FieldValueWriter.registerWriter('DatetimeNoTzTypeMySQL', (val, database) => { + // @ts-ignore + const timezone = database.options.rawTimezone || '+00:00'; + + if (typeof val === 'string' && isIso8601(val)) { + const momentVal = moment(val).utcOffset(timezone); + val = momentVal.format('YYYY-MM-DD HH:mm:ss'); + } + + return val; +}); + FieldValueWriter.registerWriter(DataTypes.BOOLEAN.toString(), (val) => Boolean(val)); + +function isIso8601(str) { + const iso8601StrictRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; + return iso8601StrictRegex.test(str); +} diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-backup-restore/src/server/locale/zh-CN.json new file mode 100644 index 0000000000..37d2086a2b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/locale/zh-CN.json @@ -0,0 +1,8 @@ +{ + "Select Import data": "请选择导入数据", + "Select Import Plugins": "请选择导入插件", + "Select User Collections": "请选择用户数据", + "Basic Data": "基础数据", + "Optional Data": "可选数据", + "User Data": "用户数据" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/locale/zh-CN.ts deleted file mode 100644 index 14bd27f3d7..0000000000 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/locale/zh-CN.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'Select Import data': '请选择导入数据', - 'Select Import Plugins': '请选择导入插件', - 'Select User Collections': '请选择用户数据', - 'Basic Data': '基础数据', - 'Optional Data': '可选数据', - 'User Data': '用户数据', -}; diff --git a/packages/plugins/@nocobase/plugin-backup-restore/src/server/restorer.ts b/packages/plugins/@nocobase/plugin-backup-restore/src/server/restorer.ts index 1d5dddfb20..f0481cf895 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/src/server/restorer.ts +++ b/packages/plugins/@nocobase/plugin-backup-restore/src/server/restorer.ts @@ -339,7 +339,7 @@ export class Restorer extends AppMigrator { .map((val, index) => [columns[index], val]) .reduce((carry, [column, val]) => { const field = fieldAttributes[column]; - carry[column] = field ? FieldValueWriter.write(field, val) : val; + carry[column] = field ? FieldValueWriter.write(field, val, app.db) : val; return carry; }, {}), diff --git a/packages/plugins/@nocobase/plugin-block-iframe/README.md b/packages/plugins/@nocobase/plugin-block-iframe/README.md index 490d73457c..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-block-iframe/README.md +++ b/packages/plugins/@nocobase/plugin-block-iframe/README.md @@ -1,11 +1,30 @@ -# iframe-block +# NocoBase -English | [中文](./README.zh-CN.md) + -Iframe 区块插件。 -## 安装激活 +## What is NocoBase -内置插件无需手动安装激活。 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 使用方法 +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-block-iframe/README.zh-CN.md b/packages/plugins/@nocobase/plugin-block-iframe/README.zh-CN.md deleted file mode 100644 index 2b000415fc..0000000000 --- a/packages/plugins/@nocobase/plugin-block-iframe/README.zh-CN.md +++ /dev/null @@ -1,11 +0,0 @@ -# iframe-block - -[English](./README.md) | 中文 - -Iframe 区块插件。 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-block-iframe/package.json b/packages/plugins/@nocobase/plugin-block-iframe/package.json index df9f349994..19ba69f8dc 100644 --- a/packages/plugins/@nocobase/plugin-block-iframe/package.json +++ b/packages/plugins/@nocobase/plugin-block-iframe/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "区块:iframe", "description": "Create an iframe block on the page to embed and display external web pages or content.", "description.zh-CN": "在页面上创建和管理iframe,用于嵌入和展示外部网页或内容。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/block-iframe", diff --git a/packages/plugins/@nocobase/plugin-block-iframe/src/client/schemaSettings.tsx b/packages/plugins/@nocobase/plugin-block-iframe/src/client/schemaSettings.tsx index ee695c3ac4..1124cba449 100644 --- a/packages/plugins/@nocobase/plugin-block-iframe/src/client/schemaSettings.tsx +++ b/packages/plugins/@nocobase/plugin-block-iframe/src/client/schemaSettings.tsx @@ -47,7 +47,7 @@ const commonOptions: any = { useComponentProps() { const field = useField(); const fieldSchema = useFieldSchema(); - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const { dn } = useDesignable(); const api = useAPIClient(); const { mode, url, params, htmlId, height = '60vh', engine } = fieldSchema['x-component-props'] || {}; @@ -92,8 +92,14 @@ const commonOptions: any = { <> {t('Syntax references')}: - - + {' '} + Handlebars.js diff --git a/packages/plugins/@nocobase/plugin-block-workbench/README.md b/packages/plugins/@nocobase/plugin-block-workbench/README.md index 4c6f516d1e..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/README.md +++ b/packages/plugins/@nocobase/plugin-block-workbench/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-block-workbench +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-block-workbench/package.json b/packages/plugins/@nocobase/plugin-block-workbench/package.json index 83d51ecfcf..4fd7c552cd 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/package.json +++ b/packages/plugins/@nocobase/plugin-block-workbench/package.json @@ -1,10 +1,10 @@ { "name": "@nocobase/plugin-block-workbench", - "version": "1.4.0-alpha", - "displayName": "Block: Workbench", - "displayName.zh-CN": "区块:工作台", - "description": "Add buttons for actions, links, etc. in the workbench block to quickly initiate actions and jump pages.", - "description.zh-CN": "在工作台区块中添加操作、链接等按钮,以便快捷发起操作和跳转页面。", + "version": "1.4.0-alpha.11", + "displayName": "Block: Action panel", + "displayName.zh-CN": "区块:操作面板", + "description": "Centrally manages and displays various actions, allowing users to efficiently perform tasks. It supports extensibility, with current action types including pop-ups, links, scanning, and custom requests.", + "description.zh-CN": "集中管理和展示各种操作,方便用户快速执行任务。支持扩展,目前支持的操作类型有弹窗、链接、扫描、自定义请求。", "main": "dist/server/index.js", "peerDependencies": { "@nocobase/client": "1.x", diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/SchemaSettingsBlockTitleItem.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/SchemaSettingsBlockTitleItem.tsx new file mode 100644 index 0000000000..344c77f250 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/SchemaSettingsBlockTitleItem.tsx @@ -0,0 +1,56 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDesignable, SchemaSettingsModalItem } from '@nocobase/client'; + +export function CustomSchemaSettingsBlockTitleItem() { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn } = useDesignable(); + const { t } = useTranslation(); + + return ( + { + console.log('titleSchemaTest', fieldSchema, field); + + const componentProps = fieldSchema['x-decorator-props'] || {}; + componentProps.title = title; + fieldSchema['x-decorator-props'] = componentProps; + field.decoratorProps.title = title; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + dn.refresh(); + }} + /> + ); +} diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx index db76743c3f..0b5b5f8994 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx @@ -42,18 +42,18 @@ function Button() {
{fieldSchema.title}
) : ( - {fieldSchema.title} + {fieldSchema.title} ); } export const WorkbenchAction = withDynamicSchemaProps((props) => { const { className, ...others } = props; - const { styles, cx } = useStyles(); + const { styles, cx } = useStyles() as any; const fieldSchema = useFieldSchema(); const Component = useComponent(props?.targetComponent) || Action; return ( } diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx index cf8af52542..f30474e5f1 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx @@ -16,9 +16,10 @@ import { useSchemaInitializerRender, withDynamicSchemaProps, Icon, + useBlockHeight, } from '@nocobase/client'; -import { css, cx } from '@emotion/css'; -import { Space, List, Avatar } from 'antd'; +import { css } from '@emotion/css'; +import { Space, List, Avatar, theme } from 'antd'; import React, { createContext, useState, useEffect } from 'react'; import { WorkbenchLayout } from './workbenchBlockSettings'; @@ -66,7 +67,7 @@ const InternalIcons = () => { }, [Object.keys(fieldSchema?.properties || {}).length]); return ( -
+
{layout === WorkbenchLayout.Grid ? ( @@ -89,9 +90,10 @@ const InternalIcons = () => { .ant-list-item-meta-title { overflow: hidden; text-overflow: ellipsis; + font-size: 14px; } .ant-list-item-meta-title button { - font-size: 16px; + font-size: 14px; overflow: hidden; text-overflow: ellipsis; width: 100%; @@ -119,13 +121,31 @@ export const WorkbenchBlock: any = withDynamicSchemaProps( (props) => { const fieldSchema = useFieldSchema(); const { layout = 'grid' } = fieldSchema['x-component-props'] || {}; + const targetHeight = useBlockHeight(); + const { token } = theme.useToken(); + const { designable } = useDesignable(); return ( - - - {props.children} - - +
+
+ + + {props.children} + + +
+
); }, { displayName: 'WorkbenchBlock' }, diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/blockSchema.ts b/packages/plugins/@nocobase/plugin-block-workbench/src/client/blockSchema.ts index 3dd401ce60..a5eb480dd3 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/blockSchema.ts +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/blockSchema.ts @@ -12,6 +12,9 @@ import { ISchema } from '@nocobase/client'; export const blockSchema: ISchema = { type: 'void', 'x-decorator': 'CardItem', + 'x-decorator-props': { + title: '', + }, 'x-settings': 'blockSettings:workbench', 'x-schema-toolbar': 'BlockSchemaToolbar', 'x-component': 'WorkbenchBlock', diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx index 0b8ef4851d..202500f412 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx @@ -7,7 +7,13 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { SchemaSettings, SchemaSettingsSelectItem, useDesignable } from '@nocobase/client'; +import { + SchemaSettings, + SchemaSettingsSelectItem, + useDesignable, + SchemaSettingsBlockHeightItem, +} from '@nocobase/client'; +import { CustomSchemaSettingsBlockTitleItem } from './SchemaSettingsBlockTitleItem'; import React from 'react'; import { useField, useFieldSchema } from '@formily/react'; import { useTranslation } from 'react-i18next'; @@ -50,6 +56,14 @@ const ActionPanelLayout = () => { export const workbenchBlockSettings = new SchemaSettings({ name: 'blockSettings:workbench', items: [ + { + name: 'title', + Component: CustomSchemaSettingsBlockTitleItem, + }, + { + name: 'setTheBlockHeight', + Component: SchemaSettingsBlockHeightItem, + }, { name: 'layout', Component: ActionPanelLayout, diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-block-workbench/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-block-workbench/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-block-workbench/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-calendar/README.md b/packages/plugins/@nocobase/plugin-calendar/README.md index 5393e68a65..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-calendar/README.md +++ b/packages/plugins/@nocobase/plugin-calendar/README.md @@ -1,6 +1,30 @@ -# calendar +# NocoBase -English | [中文](./README.zh-CN.md) + -## Introduction +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-calendar/README.zh-CN.md b/packages/plugins/@nocobase/plugin-calendar/README.zh-CN.md deleted file mode 100644 index 3d25632881..0000000000 --- a/packages/plugins/@nocobase/plugin-calendar/README.zh-CN.md +++ /dev/null @@ -1,5 +0,0 @@ -# 日历 - -[English](./README.md) | 中文 - -## 简介 \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-calendar/package.json b/packages/plugins/@nocobase/plugin-calendar/package.json index bf81ad8eab..9dc14dea9b 100644 --- a/packages/plugins/@nocobase/plugin-calendar/package.json +++ b/packages/plugins/@nocobase/plugin-calendar/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-calendar", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "Calendar", "displayName.zh-CN": "日历", "description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.", @@ -17,7 +17,7 @@ "@formily/react": "2.x", "@formily/shared": "2.x", "antd": "5.x", - "antd-style": "3.4.5", + "antd-style": "3.7.1", "cron-parser": "4.4.0", "dayjs": "^1.11.8", "lodash": "^4.17.21", diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx index 6845ab7137..4e61346003 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calendar.tsx @@ -19,12 +19,13 @@ import { useProps, useToken, withDynamicSchemaProps, + useACLRoleContext, } from '@nocobase/client'; import { parseExpression } from 'cron-parser'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; import get from 'lodash/get'; -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; import { Calendar as BigCalendar, View, dayjsLocalizer } from 'react-big-calendar'; import * as dates from 'react-big-calendar/lib/utils/dates'; import { i18nt, useTranslation } from '../../locale'; @@ -214,6 +215,18 @@ const useEvents = ( ]); }; +function findCreateSchema(schema): Schema { + return schema.reduceProperties((buf, current) => { + if (current['x-component'].endsWith('Action') && current['x-action'] === 'create') { + return current; + } + if (current['x-component'].endsWith('.ActionBar')) { + return findCreateSchema(current); + } + return buf; + }, null); +} + export const Calendar: any = withDynamicSchemaProps( observer( (props: any) => { @@ -223,16 +236,27 @@ export const Calendar: any = withDynamicSchemaProps( }); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema - const { dataSource, fieldNames, showLunar } = useProps(props); + const { dataSource, fieldNames, showLunar, defaultView } = useProps(props); const height = useCalenderHeight(); const [date, setDate] = useState(new Date()); - const [view, setView] = useState('month'); + const [view, setView] = useState(props.defaultView || 'month'); const { events, enumList } = useEvents(dataSource, fieldNames, date, view); const [record, setRecord] = useState({}); const { wrapSSR, hashId, componentCls: containerClassName } = useStyle(); const parentRecordData = useCollectionParentRecordData(); const fieldSchema = useFieldSchema(); const { token } = useToken(); + //nint deal with slot select to show create popup + const { parseAction } = useACLRoleContext(); + const collection = useCollection(); + const canCreate = parseAction(`${collection.name}:create`); + const createActionSchema: Schema = useMemo(() => findCreateSchema(fieldSchema), [fieldSchema]); + const startFieldName = fieldNames?.start?.[0]; + const endFieldName = fieldNames?.end?.[0]; + + useEffect(() => { + setView(props.defaultView); + }, [props.defaultView]); const components = useMemo(() => { return { @@ -302,7 +326,16 @@ export const Calendar: any = withDynamicSchemaProps( onNavigate={setDate} onView={setView} onSelectSlot={(slotInfo) => { - console.log('onSelectSlot', slotInfo); + //nint show create popup + if (canCreate && createActionSchema) { + const record = {}; + record[startFieldName] = slotInfo.start; + record[endFieldName] = slotInfo.end; + openPopup({ + recordData: record, + customActionSchema: createActionSchema, + }); + } }} onDoubleClickEvent={() => { console.log('onDoubleClickEvent'); diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calender.Settings.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calender.Settings.tsx index e9cb4fb3a0..3cc9a7ddb4 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calender.Settings.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/calendar/Calender.Settings.tsx @@ -116,7 +116,6 @@ export const calendarBlockSettings = new SchemaSettings({ { label: t('Not selected'), value: '' }, ...fliedList.filter((item) => item.interface === 'radioGroup' || item.interface === 'select'), ]; - return { title: t('Background color field'), value: fieldNames.colorFieldName || '', @@ -138,6 +137,36 @@ export const calendarBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'defaultView', + Component: SchemaSettingsSelectItem, + useComponentProps() { + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const { dn } = useDesignable(); + return { + title: t('Default view'), + value: field['decoratorProps']['defaultView'] || 'month', + options: [ + { value: 'month', label: '月' }, + { value: 'week', label: '周' }, + { value: 'day', label: '天' }, + ], + onChange: (v) => { + field.decoratorProps.defaultView = v; + fieldSchema['x-decorator-props']['defaultView'] = v; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': field.decoratorProps, + }, + }); + dn.refresh(); + }, + }; + }, + }, { name: 'showLunar', Component: ShowLunarDesignerItem, diff --git a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx index 44f4cb7108..74fd13972d 100644 --- a/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx +++ b/packages/plugins/@nocobase/plugin-calendar/src/client/schema-initializer/CalendarBlockProvider.tsx @@ -17,7 +17,7 @@ export const CalendarBlockContext = createContext({}); CalendarBlockContext.displayName = 'CalendarBlockContext'; const InternalCalendarBlockProvider = (props) => { - const { fieldNames, showLunar } = props; + const { fieldNames, showLunar, defaultView } = props; const field = useField(); const { resource, service } = useBlockRequestContext(); @@ -30,6 +30,7 @@ const InternalCalendarBlockProvider = (props) => { resource, fieldNames, showLunar, + defaultView, fixedBlock: field?.decoratorProps?.fixedBlock, }} > @@ -83,6 +84,7 @@ export const useCalendarBlockProps = () => { return { fieldNames: ctx.fieldNames, showLunar: ctx.showLunar, + defaultView: ctx.defaultView, fixedBlock: ctx.fixedBlock, }; }; diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/en_US.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/en-US.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/en_US.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/en-US.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/es_ES.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/es-ES.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/es_ES.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/es-ES.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/fr_FR.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/fr-FR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/fr_FR.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/fr-FR.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/ja_JP.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/ja_JP.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/ko_KR.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/ko_KR.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/pt_BR.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/pt-BR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/pt_BR.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/pt-BR.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/ru_RU.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/ru-RU.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/ru_RU.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/ru-RU.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/tr_TR.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/tr-TR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/tr_TR.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/tr-TR.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/uk_UA.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/uk-UA.json similarity index 100% rename from packages/plugins/@nocobase/plugin-calendar/src/locale/uk_UA.ts rename to packages/plugins/@nocobase/plugin-calendar/src/locale/uk-UA.json diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-calendar/src/locale/zh-CN.json new file mode 100644 index 0000000000..e7f76b1bc1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-calendar/src/locale/zh-CN.json @@ -0,0 +1,53 @@ +{ + "Configure calendar": "配置日历", + "Title field": "标题字段", + "Custom title": "自定义标题", + "Show lunar": "展示农历", + "Start date field": "开始日期字段", + "End date field": "结束日期字段", + "Work week": "工作日", + "Today": "今天", + "Day": "天", + "Agenda": "列表", + "Date": "日期", + "Time": "时间", + "Event": "事件", + "None": "无", + "Calendar": "日历", + "Delete events": "删除日程", + "This event": "此日程", + "This and following events": "此日程及后续日程", + "All events": "所有日程", + "Delete this event?": "是否删除这个日程?", + "Delete Event": "删除日程", + "Calendar collection": "日历数据表", + "Create calendar block": "创建日历区块", + "Filter": "筛选", + "Configure actions": "配置操作", + "Enable actions": "启用操作", + "Turn pages": "翻页", + "Select view": "切换视图", + "Add new": "添加", + "View record": "查看数据", + "Details": "详情", + "Customize": "自定义", + "Update record": "更新数据", + "Popup": "弹窗", + "Updated successfully": "更新成功", + "Custom request": "自定义请求", + "Edit": "编辑", + "Delete": "删除", + "Print": "打印", + "Daily": "每天", + "Weekly": "每周", + "Monthly": "每月", + "Yearly": "每年", + "Repeats": "重复", + "Title": "标题", + "Month": "月", + "Week": "周", + "{{count}} more items": "{{count}} 更多事项", + "Background color field": "背景颜色字段", + "Not selected": "未选择", + "Default view": "默认视图" +} diff --git a/packages/plugins/@nocobase/plugin-calendar/src/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-calendar/src/locale/zh-CN.ts deleted file mode 100644 index b94d244e8e..0000000000 --- a/packages/plugins/@nocobase/plugin-calendar/src/locale/zh-CN.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'Configure calendar': '配置日历', - 'Title field': '标题字段', - 'Custom title': '自定义标题', - 'Show lunar': '展示农历', - 'Start date field': '开始日期字段', - 'End date field': '结束日期字段', - 'Work week': '工作日', - Today: '今天', - Day: '天', - Agenda: '列表', - Date: '日期', - Time: '时间', - Event: '事件', - None: '无', - Calendar: '日历', - 'Delete events': '删除日程', - 'This event': '此日程', - 'This and following events': '此日程及后续日程', - 'All events': '所有日程', - 'Delete this event?': '是否删除这个日程?', - 'Delete Event': '删除日程', - 'Calendar collection': '日历数据表', - 'Create calendar block': '创建日历区块', - Filter: '筛选', - 'Configure actions': '配置操作', - 'Enable actions': '启用操作', - 'Turn pages': '翻页', - 'Select view': '切换视图', - 'Add new': '添加', - 'View record': '查看数据', - 'Details': '详情', - Customize: '自定义', - 'Update record': '更新数据', - 'Popup': '弹窗', - "Updated successfully": "更新成功", - 'Custom request': '自定义请求', - 'Edit': '编辑', - Delete: '删除', - Print: '打印', - Daily: '每天', - Weekly: '每周', - Monthly: '每月', - Yearly: '每年', - Repeats: '重复', - 'Background color field': '背景颜色字段', - 'Not selected': '未选择', - "Title":"标题", - "{{count}} more items":"{{count}} 更多事项" -}; diff --git a/packages/plugins/@nocobase/plugin-charts/package.json b/packages/plugins/@nocobase/plugin-charts/package.json index d2a85c28ce..2f4dc943d8 100644 --- a/packages/plugins/@nocobase/plugin-charts/package.json +++ b/packages/plugins/@nocobase/plugin-charts/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "图表(废弃)", "description": "The plugin has been deprecated, please use the data visualization plugin instead.", "description.zh-CN": "已废弃插件,请使用数据可视化插件代替。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "./dist/server/index.js", "license": "AGPL-3.0", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-charts/src/locale/en-US.json new file mode 100644 index 0000000000..7cc9d9b715 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/en-US.json @@ -0,0 +1,22 @@ +{ + "Edit": "Edit", + "Delete": "Delete", + "Cancel": "Cancel", + "Submit": "Submit", + "Actions": "Actions", + "Title": "Title", + "Enable": "Enable", + "SAML manager": "SAML manager", + "SAML Providers": "SAML Providers", + "Redirect url": "Redirect url", + "SP entity id": "SP entity id", + "Add provider": "Add", + "Edit provider": "Edit", + "Client id": "Client id", + "Entity id or issuer": "Entity id or issuer", + "Login Url": "Login Url", + "Public cert": "Public cert", + "Delete provider": "Delete", + "Are you sure you want to delete it?": "Are you sure you want to delete it?", + "Sign in button name, which will be displayed on the sign in page": "Sign in button name, which will be displayed on the sign in page" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/en-US.ts b/packages/plugins/@nocobase/plugin-charts/src/locale/en-US.ts deleted file mode 100644 index 2db438fa05..0000000000 --- a/packages/plugins/@nocobase/plugin-charts/src/locale/en-US.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Edit: 'Edit', - Delete: 'Delete', - Cancel: 'Cancel', - Submit: 'Submit', - Actions: 'Actions', - Title: 'Title', - Enable: 'Enable', - 'SAML manager': 'SAML manager', - 'SAML Providers': 'SAML Providers', - 'Redirect url': 'Redirect url', - 'SP entity id': 'SP entity id', - 'Add provider': 'Add', - 'Edit provider': 'Edit', - 'Client id': 'Client id', - 'Entity id or issuer': 'Entity id or issuer', - 'Login Url': 'Login Url', - 'Public cert': 'Public cert', - 'Delete provider': 'Delete', - 'Are you sure you want to delete it?': 'Are you sure you want to delete it?', - 'Sign in button name, which will be displayed on the sign in page': - 'Sign in button name, which will be displayed on the sign in page', -}; diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/es-ES.ts b/packages/plugins/@nocobase/plugin-charts/src/locale/es-ES.json similarity index 86% rename from packages/plugins/@nocobase/plugin-charts/src/locale/es-ES.ts rename to packages/plugins/@nocobase/plugin-charts/src/locale/es-ES.json index a050e6bd10..66340acb07 100644 --- a/packages/plugins/@nocobase/plugin-charts/src/locale/es-ES.ts +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/es-ES.json @@ -1,13 +1,4 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { +{ "Edit": "Editar", "Delete": "Borrar", "Cancel": "Cancelar", @@ -58,4 +49,4 @@ export default { "Branch Tags/Dimensions": "Etiquetas de rama / Dimensión", "Branch Length/Metrics": "Longitud de rama / Métrica", "Please check the chart config": "Please check the chart config" -}; +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/fr-FR.json b/packages/plugins/@nocobase/plugin-charts/src/locale/fr-FR.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/fr-FR.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-charts/src/locale/ja-JP.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/ja-JP.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/ko_KR.ts b/packages/plugins/@nocobase/plugin-charts/src/locale/ko-KR.json similarity index 75% rename from packages/plugins/@nocobase/plugin-charts/src/locale/ko_KR.ts rename to packages/plugins/@nocobase/plugin-charts/src/locale/ko-KR.json index 879651bd95..724ca10099 100644 --- a/packages/plugins/@nocobase/plugin-charts/src/locale/ko_KR.ts +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/ko-KR.json @@ -1,13 +1,4 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { +{ "Edit": "편집", "Delete": "삭제", "Cancel": "취소", @@ -59,15 +50,12 @@ export default { "Branch Tags/Dimensions": "분기 태그 / 차원", "Branch Length/Metrics": "분기 길이 / 메트릭", "Please check the chart config": "차트 설정을 확인하십시오", - "1 「time」or 「Ordered Noun」 field,1 「Numerical」 field,1 「Unordered Noun」 field (optional)": - "1개의 「time」 또는 「순서가 지정된 명사」 필드, 1개의 「숫자」 필드, 1개의 「순서가 지정되지 않은 명사」 필드 (선택 사항)", - "1 「time」 or 「ordered noun」 field, 1 「value」 field, 0~ 1 「unordered noun」": - "1개의 「time」 또는 「순서가 지정된 명사」 필드, 1개의 「value」 필드, 0~1개의 「순서가 지정되지 않은 명사」", - "1 「time」 or 「ordered noun」 field, 1 「value」 field, 0 to 1 「unordered noun」": - "1개의 「time」 또는 「순서가 지정된 명사」 필드, 1개의 「value」 필드, 0에서 1개의 「순서가 지정되지 않은 명사」", + "1 「time」or 「Ordered Noun」 field,1 「Numerical」 field,1 「Unordered Noun」 field (optional)": "1개의 「time」 또는 「순서가 지정된 명사」 필드, 1개의 「숫자」 필드, 1개의 「순서가 지정되지 않은 명사」 필드 (선택 사항)", + "1 「time」 or 「ordered noun」 field, 1 「value」 field, 0~ 1 「unordered noun」": "1개의 「time」 또는 「순서가 지정된 명사」 필드, 1개의 「value」 필드, 0~1개의 「순서가 지정되지 않은 명사」", + "1 「time」 or 「ordered noun」 field, 1 「value」 field, 0 to 1 「unordered noun」": "1개의 「time」 또는 「순서가 지정된 명사」 필드, 1개의 「value」 필드, 0에서 1개의 「순서가 지정되지 않은 명사」", "1 「Unordered Noun」 field, 1 「Numeric」 field": "1개의 「순서가 지정되지 않은 명사」 필드, 1개의 「숫자」 필드", "1 「Time」 or 「Order Noun」 field, 1 「Value」 field": "1개의 「시간」 또는 「순서가 지정된 명사」 필드, 1개의 「값」 필드", "1~ 2 「Unordered Noun」 fields, 1 「Numeric」 field": "1~2개의 「순서가 지정되지 않은 명사」 필드, 1개의 「숫자」 필드", "1 「Numeric」 field, 0~ 1 「Unordered Noun」 field": "1개의 「숫자」 필드, 0~1개의 「순서가 지정되지 않은 명사」 필드", "Chart (Old)": "차트 (이전)" - }; +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/pt-BR.json b/packages/plugins/@nocobase/plugin-charts/src/locale/pt-BR.json new file mode 100644 index 0000000000..e8d1d3cc37 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/pt-BR.json @@ -0,0 +1,22 @@ +{ + "Edit": "Editar", + "Delete": "Delete", + "Cancel": "Cancelar", + "Submit": "Enviar", + "Actions": "Ações", + "Title": "Titulo", + "Enable": "Ativo", + "SAML manager": "Gerenciador SAML", + "SAML Providers": "Provedores SAML", + "Redirect url": "URL de redirecionamento", + "SP entity id": "ID de entidade do provedor de serviço (SP)", + "Add provider": "Adicionar", + "Edit provider": "Editar", + "Client id": "ID do cliente", + "Entity id or issuer": "ID de entidade ou emissor", + "Login Url": "URL de login", + "Public cert": "Certificado público", + "Delete provider": "Excluir", + "Are you sure you want to delete it?": "Tem certeza de que deseja excluí-lo?", + "Sign in button name, which will be displayed on the sign in page": "Nome do botão de login, que será exibido na página de login" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/pt-BR.ts b/packages/plugins/@nocobase/plugin-charts/src/locale/pt-BR.ts deleted file mode 100644 index 429a7d0109..0000000000 --- a/packages/plugins/@nocobase/plugin-charts/src/locale/pt-BR.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Edit: 'Editar', - Delete: 'Delete', - Cancel: 'Cancelar', - Submit: 'Enviar', - Actions: 'Ações', - Title: 'Titulo', - Enable: 'Ativo', - 'SAML manager': 'Gerenciador SAML', - 'SAML Providers': 'Provedores SAML', - 'Redirect url': 'URL de redirecionamento', - 'SP entity id': 'ID de entidade do provedor de serviço (SP)', - 'Add provider': 'Adicionar', - 'Edit provider': 'Editar', - 'Client id': 'ID do cliente', - 'Entity id or issuer': 'ID de entidade ou emissor', - 'Login Url': 'URL de login', - 'Public cert': 'Certificado público', - 'Delete provider': 'Excluir', - 'Are you sure you want to delete it?': 'Tem certeza de que deseja excluí-lo?', - 'Sign in button name, which will be displayed on the sign in page': - 'Nome do botão de login, que será exibido na página de login', -}; diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/ru-RU.json b/packages/plugins/@nocobase/plugin-charts/src/locale/ru-RU.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/ru-RU.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/ru-RU.ts b/packages/plugins/@nocobase/plugin-charts/src/locale/ru-RU.ts deleted file mode 100644 index f3f1abbf93..0000000000 --- a/packages/plugins/@nocobase/plugin-charts/src/locale/ru-RU.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default {}; diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/tr-TR.json b/packages/plugins/@nocobase/plugin-charts/src/locale/tr-TR.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/tr-TR.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/tr-TR.ts b/packages/plugins/@nocobase/plugin-charts/src/locale/tr-TR.ts deleted file mode 100644 index f3f1abbf93..0000000000 --- a/packages/plugins/@nocobase/plugin-charts/src/locale/tr-TR.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default {}; diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-charts/src/locale/zh-CN.json new file mode 100644 index 0000000000..d1e38ea407 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-charts/src/locale/zh-CN.json @@ -0,0 +1,61 @@ +{ + "Edit": "编辑", + "Delete": "删除", + "Cancel": "取消", + "Submit": "提交", + "Actions": "操作", + "Title": "名称", + "Enable": "启用", + "Chart": "图表", + "Charts": "图表", + "Queries": "查询列表", + "Select chart query": "选择图表数据", + "Select query data": "选择查询数据", + "Type": "类型", + "Chart type": "图表类型", + "Chart title": "图表标题", + "Basic charts": "基础图表", + "More charts": "更多图表", + "Chart config": "图表配置", + "Add query": "添加查询", + "Edit query": "编辑查询", + "Invalid JSON format,must be an object array.": "无效的JSON格式,必须是对象数组。", + "Area": "面积图", + "Bar": "条形图", + "Column": "柱状图", + "Funnel": "漏斗图", + "Line": "折线图", + "Pie": "饼图", + "Radar": "雷达图", + "Scatter": "散点图", + "Edit chart block": "编辑图表区块", + "Chart preview": "图表预览", + "Delete queries": "删除查询列表", + "Delete query": "删除查询", + "Add chart query": "添加图表查询", + "Add SQL query": "添加SQL查询", + "Add JSON query": "添加JSON查询", + "Data preview": "数据预览", + "Category axis / Dimension": "类别轴 / 维度", + "Value axis / Metrics": "值轴 / 度量", + "JSON config": "JSON 配置", + "Json config references": "JSON配置参考", + "Create chart block": "创建图表区块", + "Invalid JSON format": "无效的JSON格式", + "Json config references: ": "JSON配置参考: ", + "Sector Angle / Metric": "扇形角 / 度量", + "Sector label / Dimensional": "扇形标签 / 维度", + "Color legend / Dimensional": "颜色系列 / 维度", + "Funnel Layer Width/Metrics": "漏斗层宽度 / 度量", + "Branch Tags/Dimensions": "分支标签 / 维度", + "Branch Length/Metrics": "分支长度 / 度量", + "Please check the chart config": "请检查图表配置", + "1 「time」or 「Ordered Noun」 field,1 「Numerical」 field,1 「Unordered Noun」 field (optional)": "1 个「时间」或「有序名词」字段,1 个「数值」字段,1 个「无序名词」字段(可选)", + "1 「time」 or 「ordered noun」 field, 1 「value」 field, 0~ 1 「unordered noun」": "1 个「时间」或「有序名词」字段,1 个「数值」字段,0 ~ 1 个「无序名词」", + "1 「time」 or 「ordered noun」 field, 1 「value」 field, 0 to 1 「unordered noun」": "1 个「时间」或「有序名词」字段,1 个「数值」字段,0 ~ 1 个「无序名词」", + "1 「Unordered Noun」 field, 1 「Numeric」 field": "1 个「无序名词」字段,1 个「数值」字段", + "1 「Time」 or 「Order Noun」 field, 1 「Value」 field": "1 个「时间」或「有序名词」字段,1 个「数值」字段", + "1~ 2 「Unordered Noun」 fields, 1 「Numeric」 field": "1 ~ 2 个「无序名词」字段,1 个「数值」字段", + "1 「Numeric」 field, 0~ 1 「Unordered Noun」 field": "1 个「数值」字段,0 ~ 1 个「无序名词」字段", + "Chart (Old)": "图表 (旧)" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-charts/src/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-charts/src/locale/zh-CN.ts deleted file mode 100644 index 832e5d26f6..0000000000 --- a/packages/plugins/@nocobase/plugin-charts/src/locale/zh-CN.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Edit: '编辑', - Delete: '删除', - Cancel: '取消', - Submit: '提交', - Actions: '操作', - Title: '名称', - Enable: '启用', - Chart: '图表', - Charts: '图表', - Queries: '查询列表', - 'Select chart query': '选择图表数据', - 'Select query data': '选择查询数据', - Type: '类型', - 'Chart type': '图表类型', - 'Chart title': '图表标题', - 'Basic charts': '基础图表', - 'More charts': '更多图表', - 'Chart config': '图表配置', - 'Add query': '添加查询', - 'Edit query': '编辑查询', - 'Invalid JSON format,must be an object array.': '无效的JSON格式,必须是对象数组。', - Area: '面积图', - Bar: '条形图', - Column: '柱状图', - Funnel: '漏斗图', - Line: '折线图', - Pie: '饼图', - Radar: '雷达图', - Scatter: '散点图', - 'Edit chart block': '编辑图表区块', - 'Chart preview': '图表预览', - 'Delete queries': '删除查询列表', - 'Delete query': '删除查询', - 'Add chart query': '添加图表查询', - 'Add SQL query': '添加SQL查询', - 'Add JSON query': '添加JSON查询', - 'Data preview': '数据预览', - 'Category axis / Dimension': '类别轴 / 维度', - 'Value axis / Metrics': '值轴 / 度量', - 'JSON config': 'JSON 配置', - 'Json config references': 'JSON配置参考', - 'Create chart block': '创建图表区块', - 'Invalid JSON format': '无效的JSON格式', - 'Json config references: ': 'JSON配置参考: ', - 'Sector Angle / Metric': '扇形角 / 度量', - 'Sector label / Dimensional': '扇形标签 / 维度', - 'Color legend / Dimensional': '颜色系列 / 维度', - 'Funnel Layer Width/Metrics': '漏斗层宽度 / 度量', - 'Branch Tags/Dimensions': '分支标签 / 维度', - 'Branch Length/Metrics': '分支长度 / 度量', - 'Please check the chart config': '请检查图表配置', - '1 「time」or 「Ordered Noun」 field,1 「Numerical」 field,1 「Unordered Noun」 field (optional)': - '1 个「时间」或「有序名词」字段,1 个「数值」字段,1 个「无序名词」字段(可选)', - '1 「time」 or 「ordered noun」 field, 1 「value」 field, 0~ 1 「unordered noun」': - '1 个「时间」或「有序名词」字段,1 个「数值」字段,0 ~ 1 个「无序名词」', - '1 「time」 or 「ordered noun」 field, 1 「value」 field, 0 to 1 「unordered noun」': - '1 个「时间」或「有序名词」字段,1 个「数值」字段,0 ~ 1 个「无序名词」', - '1 「Unordered Noun」 field, 1 「Numeric」 field': '1 个「无序名词」字段,1 个「数值」字段', - '1 「Time」 or 「Order Noun」 field, 1 「Value」 field': '1 个「时间」或「有序名词」字段,1 个「数值」字段', - '1~ 2 「Unordered Noun」 fields, 1 「Numeric」 field': '1 ~ 2 个「无序名词」字段,1 个「数值」字段', - '1 「Numeric」 field, 0~ 1 「Unordered Noun」 field': '1 个「数值」字段,0 ~ 1 个「无序名词」字段', - 'Chart (Old)': '图表 (旧)', -}; diff --git a/packages/plugins/@nocobase/plugin-client/README.md b/packages/plugins/@nocobase/plugin-client/README.md index cc7dc122ce..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-client/README.md +++ b/packages/plugins/@nocobase/plugin-client/README.md @@ -1,9 +1,30 @@ -# client +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-client/README.zh-CN.md b/packages/plugins/@nocobase/plugin-client/README.zh-CN.md deleted file mode 100644 index 7eb06a44ea..0000000000 --- a/packages/plugins/@nocobase/plugin-client/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# client - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-client/package.json b/packages/plugins/@nocobase/plugin-client/package.json index d60529c745..42864d514b 100644 --- a/packages/plugins/@nocobase/plugin-client/package.json +++ b/packages/plugins/@nocobase/plugin-client/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "WEB 客户端", "description": "Provides a client interface for the NocoBase server", "description.zh-CN": "为 NocoBase 服务端提供客户端界面", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "./dist/server/index.js", "license": "AGPL-3.0", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-collection-sql/README.md b/packages/plugins/@nocobase/plugin-collection-sql/README.md index 2c8f2016c0..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-collection-sql/README.md +++ b/packages/plugins/@nocobase/plugin-collection-sql/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-collection-sql +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-collection-sql/package.json b/packages/plugins/@nocobase/plugin-collection-sql/package.json index 2975d65496..31ceedda75 100644 --- a/packages/plugins/@nocobase/plugin-collection-sql/package.json +++ b/packages/plugins/@nocobase/plugin-collection-sql/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表: SQL", "description": "Provides SQL collection template", "description.zh-CN": "提供 SQL 数据表模板", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "homepage": "https://docs-cn.nocobase.com/handbook/collection-sql", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/collection-sql", "main": "dist/server/index.js", diff --git a/packages/plugins/@nocobase/plugin-collection-tree/README.md b/packages/plugins/@nocobase/plugin-collection-tree/README.md index 281ffca119..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-collection-tree/README.md +++ b/packages/plugins/@nocobase/plugin-collection-tree/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-collection-tree +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-collection-tree/package.json b/packages/plugins/@nocobase/plugin-collection-tree/package.json index 05406659b2..f7ba9e20da 100644 --- a/packages/plugins/@nocobase/plugin-collection-tree/package.json +++ b/packages/plugins/@nocobase/plugin-collection-tree/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-collection-tree", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "Collection: Tree", "displayName.zh-CN": "数据表:树", "description": "Provides tree collection template", diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/path.test.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/path.test.ts index c53e3e27fb..894b68943b 100644 --- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/path.test.ts +++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/path.test.ts @@ -570,4 +570,46 @@ describe('tree path test', () => { // await treeCollection.removeFromDb(); // expect(await db.getCollection(name).existsInDb()).toBeFalsy(); // }) + + it('should update paths when remove children', async () => { + const data = await treeCollection.repository.create({ + values: { + name: 'a1', + children: [ + { + name: 'b1', + }, + { + name: 'b2', + }, + ], + }, + }); + const b1 = data.get('children')[0]; + const b2 = data.get('children')[1]; + const tks = [b1.get(treeCollection.filterTargetKey), b2.get(treeCollection.filterTargetKey)]; + const paths = await db.getRepository(name).find({ + filter: { + [nodePkColumnName]: { + $in: tks, + }, + }, + }); + expect(paths.length).toBe(2); + expect(paths[0].get('path')).toBe('/1/2'); + expect(paths[1].get('path')).toBe('/1/3'); + // @ts-ignore + await db.getRepository(`${treeCollection.name}.children`, data.get(treeCollection.filterTargetKey)).remove(tks); + const paths2 = await db.getRepository(name).find({ + filter: { + [nodePkColumnName]: { + $in: tks, + }, + }, + }); + console.log(paths2); + expect(paths2.length).toBe(2); + expect(paths2[0].get('path')).toBe('/2'); + expect(paths2[1].get('path')).toBe('/3'); + }); }); diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts index 84f1ec71a7..0a5feb442f 100644 --- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts +++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/__tests__/sync.test.ts @@ -10,6 +10,7 @@ import { Repository } from '@nocobase/database'; import { MockDatabase, MockServer, createMockServer } from '@nocobase/test'; import Migration from '../migrations/20240802141435-collection-tree'; +import { isPg } from '@nocobase/test'; describe('tree collection sync', async () => { let app: MockServer; @@ -51,6 +52,45 @@ describe('tree collection sync', async () => { expect(pathCollection).toBeTruthy(); expect(await pathCollection.existsInDb()).toBeTruthy(); }); + + it.runIf(isPg())('should create path collection when creating inherit tree collection', async () => { + const root = db.collection({ + name: 'root', + fields: [ + { name: 'name', type: 'string' }, + { + name: 'bs', + type: 'hasMany', + target: 'b', + foreignKey: 'root_id', + }, + ], + }); + await root.sync(); + + const collection = db.collection({ + name: 'test_tree', + tree: 'adjacency-list', + inherits: ['root'], + fields: [ + { + type: 'belongsTo', + name: 'parent', + treeParent: true, + }, + { + type: 'hasMany', + name: 'children', + treeChildren: true, + }, + ], + }); + await collection.sync(); + const name = `main_${collection.name}_path`; + const pathCollection = db.getCollection(name); + expect(pathCollection).toBeTruthy(); + expect(await pathCollection.existsInDb()).toBeTruthy(); + }); }); describe('collection tree migrate test', () => { diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts index 480ed9651c..61772f45ab 100644 --- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts +++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts @@ -16,16 +16,20 @@ export default class extends Migration { on = 'afterLoad'; // 'beforeLoad' or 'afterLoad' appVersion = '<=1.3.0-beta'; + async getTreeCollections({ transaction }) { + const treeCollections = await this.app.db.getRepository('collections').find({ + appends: ['fields'], + filter: { + 'options.tree': 'adjacencyList', + }, + transaction, + }); + return treeCollections; + } + async up() { await this.db.sequelize.transaction(async (transaction) => { - const treeCollections = await this.app.db.getRepository('collections').find({ - appends: ['fields'], - filter: { - 'options.tree': 'adjacencyList', - }, - transaction, - }); - + const treeCollections = await this.getTreeCollections({ transaction }); for (const treeCollection of treeCollections) { const name = `main_${treeCollection.name}_path`; const collectionOptions = { @@ -43,11 +47,18 @@ export default class extends Migration { }, ], }; - if (treeCollection.options.schema) { - collectionOptions['schema'] = treeCollection.options.schema; + + const collectionInstance = this.db.getCollection(treeCollection.name); + const treeCollectionSchema = collectionInstance.collectionSchema(); + + if (this.app.db.inDialect('postgres') && treeCollectionSchema != this.app.db.options.schema) { + collectionOptions['schema'] = treeCollectionSchema; } + this.app.db.collection(collectionOptions); + const treeExistsInDb = await this.app.db.getCollection(name).existsInDb({ transaction }); + if (!treeExistsInDb) { await this.app.db.getCollection(name).sync({ transaction } as SyncOptions); const opts = { @@ -59,9 +70,11 @@ export default class extends Migration { { type: 'integer', name: 'parentId' }, ], }; - if (treeCollection.options.schema) { - opts['schema'] = treeCollection.options.schema; + + if (treeCollectionSchema != this.app.db.options.schema) { + opts['schema'] = treeCollectionSchema; } + this.app.db.collection(opts); const chunkSize = 1000; await this.app.db.getRepository(treeCollection.name).chunk({ diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20241022161700-inherit-collection-tree.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20241022161700-inherit-collection-tree.ts new file mode 100644 index 0000000000..20de58b5ba --- /dev/null +++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20241022161700-inherit-collection-tree.ts @@ -0,0 +1,26 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import Migration from './20240802141435-collection-tree'; + +export default class extends Migration { + on = 'afterLoad'; // 'beforeLoad' or 'afterLoad' + appVersion = '<=1.3.36-beta'; + + async getTreeCollections({ transaction }) { + const treeCollections = await this.app.db.getRepository('collections').find({ + appends: ['fields'], + filter: { + 'options.tree': 'adjacencyList', + }, + transaction, + }); + return treeCollections.filter((collection) => collection.options.inherits?.length > 0); + } +} diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts index 7fce8610e4..945b558a40 100644 --- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/plugin.ts @@ -14,11 +14,6 @@ import lodash from 'lodash'; import { Transaction } from 'sequelize'; import { TreeCollection } from './tree-collection'; -const getFilterTargetKey = (model: Model) => { - // @ts-ignore - return model.constructor.collection.filterTargetKey; -}; - class PluginCollectionTreeServer extends Plugin { async beforeLoad() { const condition = (options) => { @@ -55,7 +50,7 @@ class PluginCollectionTreeServer extends Plugin { //afterCreate this.db.on(`${collection.name}.afterCreate`, async (model: Model, options) => { const { transaction } = options; - const tk = getFilterTargetKey(model); + const tk = collection.filterTargetKey as string; let path = `/${model.get(tk)}`; path = await this.getTreePath(model, path, collection, name, transaction); const rootPk = path.split('/')[1]; @@ -71,49 +66,35 @@ class PluginCollectionTreeServer extends Plugin { //afterUpdate this.db.on(`${collection.name}.afterUpdate`, async (model: Model, options) => { - const tk = getFilterTargetKey(model); + const tk = collection.filterTargetKey; // only update parentId and filterTargetKey if (!(model._changed.has(tk) || model._changed.has(parentForeignKey))) { return; } const { transaction } = options; - let path = `/${model.get(tk)}`; - path = await this.getTreePath(model, path, collection, name, transaction); - const collectionTreePath = this.db.getCollection(name); - const nodePkColumnName = collectionTreePath.getField('nodePk').columnName(); - const pathData = await this.app.db.getRepository(name).findOne({ - filter: { - [nodePkColumnName]: model.get(tk), - }, - transaction, - }); + await this.updateTreePath(model, collection, name, transaction); + }); - const relatedNodes = await this.app.db.getRepository(name).find({ - filter: { - path: { - $startsWith: `${pathData.get('path')}`, - }, + // after remove + this.db.on(`${collection.name}.afterBulkUpdate`, async (options) => { + const tk = collection.filterTargetKey as string; + if (!(options.where && options.where[tk])) { + return; + } + const instances = await this.db.getRepository(collection.name).find({ + where: { + [tk]: options.where[tk], }, - transaction, + transaction: options.transaction, }); - const rootPk = path.split('/')[1]; - for (const node of relatedNodes) { - await this.app.db.getRepository(name).update({ - values: { - path: node.get('path').replace(`${pathData.get('path')}`, path), - rootPk: rootPk ? Number(rootPk) : null, - }, - filter: { - [nodePkColumnName]: node.get('nodePk'), - }, - transaction, - }); + for (const model of instances) { + await this.updateTreePath(model, collection, name, options.transaction); } }); //afterDestroy this.db.on(`${collection.name}.afterDestroy`, async (model: Model, options: DestroyOptions) => { - const tk = getFilterTargetKey(model); + const tk = collection.filterTargetKey as string; await this.app.db.getRepository(name).destroy({ filter: { nodePk: model.get(tk), @@ -164,7 +145,7 @@ class PluginCollectionTreeServer extends Plugin { pathCollectionName: string, transaction?: Transaction, ) { - const tk = getFilterTargetKey(model); + const tk = collection.filterTargetKey as string; const parentForeignKey = collection.treeParentField?.foreignKey || 'parentId'; if (model.get(parentForeignKey) && model.get(parentForeignKey) !== null) { const parent = await this.app.db.getRepository(collection.name).findOne({ @@ -195,6 +176,48 @@ class PluginCollectionTreeServer extends Plugin { } return path; } + + private async updateTreePath( + model: Model, + collection: Collection, + pathCollectionName: string, + transaction: Transaction, + ) { + const tk = collection.filterTargetKey as string; + let path = `/${model.get(tk)}`; + path = await this.getTreePath(model, path, collection, pathCollectionName, transaction); + const collectionTreePath = this.db.getCollection(pathCollectionName); + const nodePkColumnName = collectionTreePath.getField('nodePk').columnName(); + const pathData = await this.app.db.getRepository(pathCollectionName).findOne({ + filter: { + [nodePkColumnName]: model.get(tk), + }, + transaction, + }); + + const relatedNodes = await this.app.db.getRepository(pathCollectionName).find({ + filter: { + path: { + $startsWith: `${pathData.get('path')}`, + }, + }, + transaction, + }); + const rootPk = path.split('/')[1]; + for (const node of relatedNodes) { + const newPath = node.get('path').replace(pathData.get('path'), path); + await this.app.db.getRepository(pathCollectionName).update({ + values: { + path: newPath, + rootPk: rootPk ? Number(rootPk) : null, + }, + filter: { + [nodePkColumnName]: node.get('nodePk'), + }, + transaction, + }); + } + } } export default PluginCollectionTreeServer; diff --git a/packages/plugins/@nocobase/plugin-data-source-main/README.md b/packages/plugins/@nocobase/plugin-data-source-main/README.md index 43b192faee..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/README.md +++ b/packages/plugins/@nocobase/plugin-data-source-main/README.md @@ -1,9 +1,30 @@ -# collection-manager +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-data-source-main/README.zh-CN.md b/packages/plugins/@nocobase/plugin-data-source-main/README.zh-CN.md deleted file mode 100644 index 3140973a84..0000000000 --- a/packages/plugins/@nocobase/plugin-data-source-main/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# collection-manager - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-data-source-main/package.json b/packages/plugins/@nocobase/plugin-data-source-main/package.json index 48b3cb19b9..45f5830afc 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/package.json +++ b/packages/plugins/@nocobase/plugin-data-source-main/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据源:主数据库", "description": "NocoBase main database, supports relational databases such as PostgreSQL, MySQL, MariaDB and so on.", "description.zh-CN": "NocoBase 主数据库,支持 PostgreSQL、MySQL、MariaDB 等关系型数据库。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/data-source-main", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main", diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts index 2d81ec58ea..f602be81e9 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts @@ -96,7 +96,7 @@ test.describe('form item & create form', () => { .getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-oneToOneBelongsTo') .getByTestId(/select-object/) .click(); - await page.getByRole('option', { name: String(records[0].id), exact: true }).click(); + await page.getByTitle(String(records[0].id), { exact: true }).click(); }, expectReadonly: async () => { await expect( @@ -165,7 +165,7 @@ test.describe('form item & create form', () => { .getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-oneToOneBelongsTo') .getByTestId('select-object-single') .click(); - await expect(page.getByRole('option', { name: String(records[0].id), exact: true })).toBeVisible(); + await expect(page.getByTitle(String(records[0].id), { exact: true })).toBeVisible(); await expect(page.getByRole('option')).toHaveCount(1); }); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts index 1a06b327e7..29dd7e2812 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts @@ -155,7 +155,7 @@ test.describe('form item & edit form', () => { .getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-oneToOneBelongsTo') .getByTestId(/select-object/) .click(); - await expect(page.getByRole('option', { name: String(recordsOfUser[0].id), exact: true })).toBeVisible(); + await expect(page.getByTitle(String(recordsOfUser[0].id), { exact: true })).toBeVisible(); await expect(page.getByRole('option')).toHaveCount(2); }); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/checkbox/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/checkbox/schemaSettings.test.ts index 19c231581c..a836a124ff 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/checkbox/schemaSettings.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/checkbox/schemaSettings.test.ts @@ -159,7 +159,7 @@ test.describe('form item & edit form', () => { gotoPage: async () => { record = await (async (mockPage, mockRecord) => { const nocoPage = await mockPage(oneTableBlockWithAddNewAndViewAndEditAndChoicesFields).waitForInit(); - const record = await mockRecord('general'); + const record = await mockRecord('general', { checkbox: false }); await nocoPage.goto(); return record; @@ -192,15 +192,18 @@ test.describe('form item & edit form', () => { ).toBeDisabled(); }, expectEasyReading: async () => { - // checkbox 应该被隐藏,然后只显示一个图标 - await expect( - page.getByLabel('block-item-CollectionField-general-form-general.checkbox-checkbox').getByRole('checkbox'), - ).not.toBeVisible(); - await expect( - page - .getByLabel('block-item-CollectionField-general-form-general.checkbox-checkbox') - .getByRole('img', { name: 'check' }), - ).toBeVisible({ visible: record.checkbox }); + if (record.checkbox) { + await expect( + page + .getByLabel('block-item-CollectionField-general-form-general.checkbox-checkbox') + .getByRole('img', { name: 'check' }), + ).toBeVisible({ visible: record.checkbox }); + } else { + // 未选中状态会显示一个禁用的 checkbox + await expect( + page.getByLabel('block-item-CollectionField-general-form-general.checkbox-checkbox').getByRole('checkbox'), + ).toBeDisabled(); + } }, }); }); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/manyToOne/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/manyToOne/schemaSettings.test.ts index eab7b5adb2..e7e8ebafe9 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/manyToOne/schemaSettings.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/manyToOne/schemaSettings.test.ts @@ -11,6 +11,7 @@ import { expect, expectSettingsMenu, oneFilterFormBlockWithAllAssociationFields, + oneFilterFormBlockWithAllAssociationFieldsV1333Beta, oneTableBlockWithAddNewAndViewAndEditAndAssociationFields, test, } from '@nocobase/test/e2e'; @@ -38,6 +39,28 @@ test.describe('form item & filter form', () => { ], }); }); + + test('v1.3: supported options', async ({ page, mockPage }) => { + const nocoPage = await mockPage(oneFilterFormBlockWithAllAssociationFieldsV1333Beta).waitForInit(); + await nocoPage.goto(); + + await expectSettingsMenu({ + page, + showMenu: async () => { + await page.getByLabel('block-item-CollectionField-general-filter-form-general.manyToOne-manyToOne').hover(); + await page.getByRole('button', { name: 'designer-schema-settings-CollectionField' }).hover(); + }, + supportedOptions: [ + 'Edit field title', + 'Edit description', + 'Set the data scope', + 'Field component', + 'Title field', + 'Delete', + 'Allow multiple selection', + ], + }); + }); }); test.describe('table column & table', () => { diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts index e14f1c6ed4..b4b800e814 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts @@ -331,8 +331,8 @@ export class CollectionSettings { } private async ['File storage'](value: string) { - await this.page.getByLabel('block-item-Select-collections-File storage').getByTestId('select-single').click(); - await this.page.getByRole('option', { name: value }).click(); + await this.page.getByLabel('block-item-RemoteSelect-collections-File storage').getByTestId('select-single').click(); + await this.page.getByTitle(value).click(); } private async ['Collection display name'](value: string) { diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-data-source-main/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-data-source-main/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-data-source-main/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts index 52680993c4..1650391393 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts @@ -7,10 +7,11 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import Database, { CollectionGroupManager, Collection as DBCollection, HasManyRepository } from '@nocobase/database'; +import Database, { Collection as DBCollection, CollectionGroupManager, HasManyRepository } from '@nocobase/database'; import Application from '@nocobase/server'; import { createApp } from '.'; -import CollectionManagerPlugin, { CollectionRepository } from '../index'; +import { CollectionRepository } from '../index'; +import { isPg } from '@nocobase/test'; describe('collections repository', () => { let db: Database; @@ -26,6 +27,7 @@ describe('collections repository', () => { }); afterEach(async () => { + vi.unstubAllEnvs(); await app.destroy(); }); @@ -380,13 +382,8 @@ describe('collections repository', () => { expect(afterRepository.load).toBeTruthy(); }); - it('should set collection schema from env', async () => { - if (!db.inDialect('postgres')) { - return; - } - - const plugin = app.getPlugin('data-source-main'); - plugin.schema = 'testSchema'; + it.runIf(isPg())('should set collection schema from env', async () => { + vi.stubEnv('COLLECTION_MANAGER_SCHEMA', 'testSchema'); await Collection.repository.create({ values: { diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts index dd1896b108..9706b5c9d2 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts @@ -206,18 +206,20 @@ describe('belongsToMany', () => { context: {}, }); - const throughCollection = await Collection.repository.findOne({ + const throughCollectionRecord = await Collection.repository.findOne({ filter: { name: 'post_tags', }, }); - expect(throughCollection.get('sortable')).toEqual(false); + expect(throughCollectionRecord.get('sortable')).toEqual(false); const collectionManagerSchema = process.env.COLLECTION_MANAGER_SCHEMA; const mainSchema = process.env.DB_SCHEMA || 'public'; + const throughCollection = db.getCollection('post_tags'); + if (collectionManagerSchema && mainSchema != collectionManagerSchema && db.inDialect('postgres')) { - expect(throughCollection.get('schema')).toEqual(collectionManagerSchema); + expect(throughCollection.options.schema).toEqual(collectionManagerSchema); const tableName = db.getCollection('post_tags').model.tableName; diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts index c4833ab77c..4af695a2e0 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/http-api/collections.test.ts @@ -591,4 +591,14 @@ describe('collections repository', () => { const collectionInMemory = app.db.getCollection(firstCollection.name); expect(firstCollection.unavailableActions).toEqual(collectionInMemory.unavailableActions()); }); + + it('should get full collections api', async () => { + const response = await app.agent().resource('collections').listMeta(); + expect(response.statusCode).toBe(200); + const data = response.body.data; + const firstCollection = data[0]; + const collectionInMemory = app.db.getCollection(firstCollection.name); + expect(firstCollection.unavailableActions).toEqual(collectionInMemory.unavailableActions()); + expect(firstCollection.fields.length).toEqual(collectionInMemory.fields.size); + }); }); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts index 18efa02366..1f46535ab7 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts @@ -63,6 +63,10 @@ export class CollectionModel extends MagicAttributeModel { delete collectionOptions.schema; } + if (this.db.inDialect('postgres') && !collectionOptions.schema && collectionOptions.from !== 'db2cm') { + collectionOptions.schema = process.env.COLLECTION_MANAGER_SCHEMA || this.db.options.schema || 'public'; + } + if (this.db.hasCollection(name)) { collection = this.db.getCollection(name); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/repositories/collection-repository.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/repositories/collection-repository.ts index 1c49e46de0..fab49bb91f 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/repositories/collection-repository.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/repositories/collection-repository.ts @@ -47,6 +47,8 @@ export class CollectionRepository extends Repository { if (instance.get('view') || instance.get('sql')) { viewCollections.push(instance.get('name')); } + + this.database.collectionsSort.set(instance.get('name'), instance.get('sort')); } // set graph edges by inherits diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/resourcers/collections.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/resourcers/collections.ts index ce349770f4..53540faedf 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/resourcers/collections.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/resourcers/collections.ts @@ -8,8 +8,44 @@ */ import { Database } from '@nocobase/database'; +import lodash from 'lodash'; export default { + async ['collections:listMeta'](ctx, next) { + const db = ctx.app.db as Database; + const results = []; + + db.collections.forEach((collection) => { + if (!collection.options.loadedFromCollectionManager) { + return; + } + + const obj = { + ...collection.options, + filterTargetKey: collection.filterTargetKey, + }; + + if (collection && collection.unavailableActions) { + obj['unavailableActions'] = collection.unavailableActions(); + } + + obj.fields = lodash.sortBy( + [...collection.fields.values()].map((field) => { + return { + ...field.options, + }; + }), + 'sort', + ); + + results.push(obj); + }); + + ctx.body = lodash.sortBy(results, 'sort'); + + await next(); + }, + async ['collections:setFields'](ctx, next) { const { filterByTk, values } = ctx.action.params; diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts index 98cf37984e..c1d7ef384e 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts @@ -12,7 +12,6 @@ import PluginErrorHandler from '@nocobase/plugin-error-handler'; import { Plugin } from '@nocobase/server'; import lodash from 'lodash'; import path from 'path'; -import * as process from 'process'; import { CollectionRepository } from '.'; import { afterCreateForForeignKeyField, @@ -32,8 +31,6 @@ import { FieldIsDependedOnByOtherError } from './errors/field-is-depended-on-by- import { beforeCreateCheckFieldInMySQL } from './hooks/beforeCreateCheckFieldInMySQL'; export class PluginDataSourceMainServer extends Plugin { - public schema: string; - private loadFilter: Filter = {}; setLoadFilter(filter: Filter) { @@ -75,10 +72,6 @@ export class PluginDataSourceMainServer extends Plugin { } async beforeLoad() { - if (this.app.db.inDialect('postgres')) { - this.schema = process.env.COLLECTION_MANAGER_SCHEMA || this.db.options.schema || 'public'; - } - this.app.db.registerRepositories({ CollectionRepository, }); @@ -96,12 +89,6 @@ export class PluginDataSourceMainServer extends Plugin { }, }); - this.app.db.on('collections.beforeCreate', async (model) => { - if (this.app.db.inDialect('postgres') && this.schema && model.get('from') != 'db2cm' && !model.get('schema')) { - model.set('schema', this.schema); - } - }); - this.app.db.on('collections.beforeCreate', beforeCreateForViewCollection(this.db)); this.app.db.on( @@ -373,7 +360,7 @@ export class PluginDataSourceMainServer extends Plugin { this.app.on('beforeStart', loadCollections); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function pushUISchemaWhenUpdateCollectionField(ctx, next) { const { resourceName, actionName } = ctx.action; if (resourceName === 'collections.fields' && actionName === 'update') { const { updateAssociationValues = [] } = ctx.action.params; @@ -386,6 +373,7 @@ export class PluginDataSourceMainServer extends Plugin { }); this.app.acl.allow('collections', 'list', 'loggedIn'); + this.app.acl.allow('collections', 'listMeta', 'loggedIn'); this.app.acl.allow('collectionCategories', 'list', 'loggedIn'); this.app.acl.registerSnippet({ @@ -453,7 +441,7 @@ export class PluginDataSourceMainServer extends Plugin { }, ); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function mergeReverseFieldWhenSaveCollectionField(ctx, next) { if (ctx.action.resourceName === 'collections.fields' && ['create', 'update'].includes(ctx.action.actionName)) { ctx.action.mergeParams({ updateAssociationValues: ['reverseField'], @@ -494,7 +482,7 @@ export class PluginDataSourceMainServer extends Plugin { } }; - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function handleFieldSourceMiddleware(ctx, next) { await next(); // handle collections:list diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/README.md b/packages/plugins/@nocobase/plugin-data-source-manager/README.md index b8dc7945c7..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/README.md +++ b/packages/plugins/@nocobase/plugin-data-source-manager/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-data-source-manager +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/package.json b/packages/plugins/@nocobase/plugin-data-source-manager/package.json index f28926b37c..236f4823f3 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/package.json +++ b/packages/plugins/@nocobase/plugin-data-source-manager/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-data-source-manager", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "displayName": "Data source manager", "displayName.zh-CN": "数据源管理", @@ -12,8 +12,7 @@ "@nocobase/client": "1.x", "@nocobase/plugin-acl": "1.x", "@nocobase/server": "1.x", - "@nocobase/test": "1.x", - "@nocobase/plugin-acl": "1.x" + "@nocobase/test": "1.x" }, "keywords": [ "Data model tools" diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/CollectionMainProvider.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/CollectionMainProvider.tsx new file mode 100644 index 0000000000..6ced78b500 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/MainDataSourceManager/CollectionMainProvider.tsx @@ -0,0 +1,39 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useCallback } from 'react'; +import { CollectionCategoriesProvider, useAPIClient, useRequest } from '@nocobase/client'; +import { Outlet } from 'react-router-dom'; + +export const CollectionMainProvider = (props) => { + const api = useAPIClient(); + + const coptions = { + url: 'collectionCategories:list', + params: { + paginate: false, + sort: ['sort'], + }, + }; + + const result = useRequest<{ + data: any; + }>(coptions); + + const refreshCategory = useCallback(async () => { + const { data } = await api.request(coptions); + result.mutate(data); + return data?.data || []; + }, [result]); + return ( + + {} + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx index 50da2caef2..77112abc34 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/index.tsx @@ -19,6 +19,7 @@ import { DatabaseConnectionManagerPane } from './component/DatabaseConnectionMan import { MainDataSourceManager } from './component/MainDataSourceManager'; import { DataSourcePermissionManager } from './component/PermissionManager'; import { NAMESPACE } from './locale'; +import { CollectionMainProvider } from './component/MainDataSourceManager/CollectionMainProvider'; export class PluginDataSourceManagerClient extends Plugin { types = new Map(); @@ -46,6 +47,7 @@ export class PluginDataSourceManagerClient extends Plugin { })); this.app.use(DatabaseConnectionProvider); + this.app.pluginSettingsManager.add(NAMESPACE, { title: `{{t("Data sources", { ns: "${NAMESPACE}" })}}`, icon: 'ClusterOutlined', @@ -74,6 +76,7 @@ export class PluginDataSourceManagerClient extends Plugin { sort: 100, skipAclConfigure: true, aclSnippet: 'pm.data-source-manager.data-source-main', + Component: CollectionMainProvider, }); this.app.pluginSettingsManager.add(`${NAMESPACE}/main.collections`, { title: `{{t("Collections", { ns: "${NAMESPACE}" })}}`, diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-data-source-manager/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-data-source-manager/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-data-source-manager/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts index 1812ab8d28..cdb08ea203 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/__tests__/data-sources.test.ts @@ -23,6 +23,86 @@ describe('data source', async () => { await app.destroy(); }); + it('should return datasource status when datasource is loading or reloading', async () => { + class MockDataSource extends DataSource { + static testConnection(options?: any): Promise { + return Promise.resolve(true); + } + + async load(): Promise { + await waitSecond(1000); + } + + createCollectionManager(options?: any): any { + return undefined; + } + } + + app.dataSourceManager.factory.register('mock', MockDataSource); + + app.dataSourceManager.beforeAddDataSource(async (dataSource: DataSource) => { + const total = 1000; + for (let i = 0; i < total; i++) { + dataSource.emitLoadingProgress({ + total, + loaded: i, + }); + } + }); + + await app.db.getRepository('dataSources').create({ + values: { + key: 'mockInstance1', + type: 'mock', + displayName: 'Mock', + options: {}, + }, + }); + + await waitSecond(200); + + // get data source status + const plugin: any = app.pm.get('data-source-manager'); + expect(plugin.dataSourceStatus['mockInstance1']).toBe('loading'); + + const loadingStatus = plugin.dataSourceLoadingProgress['mockInstance1']; + expect(loadingStatus).toBeDefined(); + }); + + it('should get error when datasource loading failed', async () => { + class MockDataSource extends DataSource { + static testConnection(options?: any): Promise { + return Promise.resolve(true); + } + + async load(): Promise { + throw new Error(`load failed`); + } + + createCollectionManager(options?: any): any { + return undefined; + } + } + + app.dataSourceManager.factory.register('mock', MockDataSource); + + await app.db.getRepository('dataSources').create({ + values: { + key: 'mockInstance1', + type: 'mock', + displayName: 'Mock', + options: {}, + }, + }); + + await waitSecond(2000); + // get data source status + const plugin: any = app.pm.get('data-source-manager'); + expect(plugin.dataSourceStatus['mockInstance1']).toBe('loading-failed'); + + expect(plugin.dataSourceErrors['mockInstance1'].message).toBe('load failed'); + }); + it('should list main datasource in api', async () => { const listResp = await app.agent().resource('dataSources').list(); expect(listResp.status).toBe(200); diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-source.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-source.ts index 463c87d318..28854c58a9 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-source.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-source.ts @@ -92,33 +92,38 @@ export class DataSourceModel extends Model { const type = this.get('type'); const createOptions = this.get('options'); - const dataSource = app.dataSourceManager.factory.create(type, { - ...createOptions, - name: dataSourceKey, - logger: app.logger.child({ dataSourceKey }), - }); - - if (loadAtAfterStart) { - dataSource.on('loadMessage', ({ message }) => { - app.setMaintainingMessage(`${message} in data source ${this.get('displayName')}`); - }); - } - - const acl = dataSource.acl; - - for (const [actionName, actionParams] of Object.entries(availableActions)) { - acl.setAvailableAction(actionName, actionParams); - } - - acl.allow('*', '*', (ctx) => { - return ctx.state.currentRole === 'root'; - }); - - dataSource.resourceManager.use(setCurrentRole, { tag: 'setCurrentRole', before: 'acl', after: 'auth' }); - - await this.loadIntoACL({ app, acl, transaction: options.transaction }); - try { + const dataSource = app.dataSourceManager.factory.create(type, { + ...createOptions, + name: dataSourceKey, + logger: app.logger.child({ dataSourceKey }), + sqlLogger: app.sqlLogger.child({ dataSourceKey }), + }); + + dataSource.on('loadingProgress', (progress) => { + pluginDataSourceManagerServer.dataSourceLoadingProgress[dataSourceKey] = progress; + }); + + if (loadAtAfterStart) { + dataSource.on('loadMessage', ({ message }) => { + app.setMaintainingMessage(`${message} in data source ${this.get('displayName')}`); + }); + } + + const acl = dataSource.acl; + + for (const [actionName, actionParams] of Object.entries(availableActions)) { + acl.setAvailableAction(actionName, actionParams); + } + + acl.allow('*', '*', (ctx) => { + return ctx.state.currentRole === 'root'; + }); + + dataSource.resourceManager.use(setCurrentRole, { tag: 'setCurrentRole', before: 'acl', after: 'auth' }); + + await this.loadIntoACL({ app, acl, transaction: options.transaction }); + await app.dataSourceManager.add(dataSource, { localData: await this.loadLocalData(), }); diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts index c4b3ca74e3..ce81a033f3 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/models/data-sources-field-model.ts @@ -20,13 +20,27 @@ export class DataSourcesFieldModel extends MagicAttributeModel { const { app } = loadOptions; const options = this.get(); - const { collectionName, name, dataSourceKey } = options; + const { collectionName, name, dataSourceKey, field } = options; const dataSource = app.dataSourceManager.dataSources.get(dataSourceKey); const collection = dataSource.collectionManager.getCollection(collectionName); - const oldField = collection.getField(name); + + const oldFieldByName = collection.getField(name); + const oldFieldByField = field ? collection.getFieldByField(field) : null; + + const oldField = oldFieldByField || oldFieldByName; const newOptions = mergeOptions(oldField ? oldField.options : {}, options); collection.setField(name, newOptions); + + if (oldFieldByField && !oldFieldByName) { + const filedShouldRemove = collection + .getFields() + .filter((f) => f.options.field === field && f.options.name !== name); + + for (const f of filedShouldRemove) { + collection.removeField(f.options.name); + } + } } unload(loadOptions: LoadOptions) { diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts index 8200b7886f..2c5f0812e0 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts @@ -22,6 +22,7 @@ import { DataSourcesRolesResourcesModel } from './models/connections-roles-resou import { DataSourcesRolesResourcesActionModel } from './models/connections-roles-resources-action'; import { DataSourceModel } from './models/data-source'; import { DataSourcesRolesModel } from './models/data-sources-roles-model'; +import { LoadingProgress } from '@nocobase/data-source-manager'; type DataSourceState = 'loading' | 'loaded' | 'loading-failed' | 'reloading' | 'reloading-failed'; @@ -126,6 +127,10 @@ export class PluginDataSourceManagerServer extends Plugin { } } + public dataSourceLoadingProgress: { + [dataSourceKey: string]: LoadingProgress; + } = {}; + async beforeLoad() { this.app.db.registerModels({ DataSourcesCollectionModel, @@ -177,6 +182,10 @@ export class PluginDataSourceManagerServer extends Plugin { const klass = this.app.dataSourceManager.factory.getClass(type); + if (!klass) { + throw new Error(`Data source type "${type}" is not registered`); + } + try { await klass.testConnection(dataSourceOptions); } catch (error) { @@ -222,7 +231,7 @@ export class PluginDataSourceManagerServer extends Plugin { const app = this.app; - this.app.use(async (ctx, next) => { + this.app.use(async function setDataSourceListItemStatus(ctx, next) { await next(); if (!ctx.action) { return; @@ -253,11 +262,11 @@ export class PluginDataSourceManagerServer extends Plugin { return data; } - const dataSourceStatus = this.dataSourceStatus[item.get('key')]; + const dataSourceStatus = plugin.dataSourceStatus[item.get('key')]; data['status'] = dataSourceStatus; if (dataSourceStatus === 'loading-failed' || dataSourceStatus === 'reloading-failed') { - data['errorMessage'] = this.dataSourceErrors[item.get('key')].message; + data['errorMessage'] = plugin.dataSourceErrors[item.get('key')].message; } return data; @@ -324,7 +333,7 @@ export class PluginDataSourceManagerServer extends Plugin { return item; }; - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function setDataSourceListDefaultSort(ctx, next) { if (!ctx.action) { await next(); return; @@ -341,7 +350,7 @@ export class PluginDataSourceManagerServer extends Plugin { await next(); }); - this.app.use(async (ctx, next) => { + this.app.use(async function handleAppendDataSourceCollection(ctx, next) { await next(); if (!ctx.action) { @@ -461,7 +470,9 @@ export class PluginDataSourceManagerServer extends Plugin { this.app.db.on('dataSourcesCollections.afterDestroy', async (model: DataSourcesCollectionModel, options) => { const dataSource = this.app.dataSourceManager.dataSources.get(model.get('dataSourceKey')); - dataSource.collectionManager.removeCollection(model.get('name')); + if (dataSource) { + dataSource.collectionManager.removeCollection(model.get('name')); + } this.sendSyncMessage( { @@ -679,7 +690,7 @@ export class PluginDataSourceManagerServer extends Plugin { }); // add global roles check - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function appendDataToRolesCheck(ctx, next) { const action = ctx.action; await next(); const { resourceName, actionName } = action.params; @@ -689,12 +700,12 @@ export class PluginDataSourceManagerServer extends Plugin { ctx.bodyMeta = { dataSources: dataSources.reduce((carry, dataSourceModel) => { - const dataSource = this.app.dataSourceManager.dataSources.get(dataSourceModel.get('key')); + const dataSource = self.app.dataSourceManager.dataSources.get(dataSourceModel.get('key')); if (!dataSource) { return carry; } - const dataSourceStatus = this.dataSourceStatus[dataSourceModel.get('key')]; + const dataSourceStatus = self.dataSourceStatus[dataSourceModel.get('key')]; if (dataSourceStatus !== 'loaded') { return carry; } diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts index 61a9aff450..eee7934032 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections-fields.ts @@ -67,20 +67,22 @@ export default { }, }); } else { - await mainDb.getRepository('dataSourcesFields').update({ - filter: { - name, - collectionName, - dataSourceKey, - }, - values, - }); + fieldRecord = ( + await mainDb.getRepository('dataSourcesFields').update({ + filter: { + name, + collectionName, + dataSourceKey, + }, + values, + }) + )[0]; } const field = ctx.app.dataSourceManager.dataSources .get(dataSourceKey) .collectionManager.getCollection(collectionName) - .getField(name); + .getField(fieldRecord.get('name')); ctx.body = field.options; diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts index 095875b335..84296c5946 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/resourcers/data-sources-collections.ts @@ -17,6 +17,29 @@ export default { const { associatedIndex: dataSourceKey } = params; const dataSource = ctx.app.dataSourceManager.dataSources.get(dataSourceKey); + const plugin: any = ctx.app.pm.get('data-source-manager'); + + const dataSourceStatus = plugin.dataSourceStatus[dataSourceKey]; + + if (dataSourceStatus === 'loading-failed') { + const error = plugin.dataSourceErrors[dataSourceKey]; + if (error) { + throw new Error(`dataSource ${dataSourceKey} loading failed: ${error.message}`); + } + + throw new Error(`dataSource ${dataSourceKey} loading failed`); + } + + if (['loading', 'reloading'].includes(dataSourceStatus)) { + const progress = plugin.dataSourceLoadingProgress[dataSourceKey]; + + if (progress) { + throw new Error(`dataSource ${dataSourceKey} is ${dataSourceStatus} (${progress.loaded}/${progress.total})`); + } + + throw new Error(`dataSource ${dataSourceKey} is ${dataSourceStatus}`); + } + if (!dataSource) { throw new Error(`dataSource ${dataSourceKey} not found`); } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/README.md b/packages/plugins/@nocobase/plugin-data-visualization/README.md index 7cf2ab5f60..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/README.md +++ b/packages/plugins/@nocobase/plugin-data-visualization/README.md @@ -1,88 +1,30 @@ -# Data Visualization +# NocoBase -提供BI面板和数据可视化功能。 + -## 介绍 -新版数据可视化插件以Collection为基础,提供了可视化的数据检索、图表配置面板,多个图表可以在同一区块内进行组织,支持以插件形式扩展和使用其他图表组件库。未来还计划支持SQL模式,单个及多个图表的时间、条件筛选,数据下钻,图表与数据区块联动等功能。 +## What is NocoBase -## 图表区块 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! - +Homepage: +https://www.nocobase.com/ -- 图表区块可以组织多个图表,区块中的图表可以像区块一样排列和拖拽。 -- 区块标题可以编辑。 -- 图表以Collection为基础,新建图表时需要选定一个Collection. -- 有查看权限的Collection才可以用于配置图表,否则将会在选项中被隐藏。 -- 图表可以修改 (Configure), 复制 (Duplicate), 设置标题 (Edit block title). +Online Demo: +https://demo.nocobase.com/new -## 配置面板 +Documents: +https://docs.nocobase.com/ - +Commericial license & plugins: +https://www.nocobase.com/en/commercial -配置面板整体上分为三个区块:数据配置,图表配置,图表预览。 +License agreement: +https://www.nocobase.com/en/agreement -### 数据配置 - - -- 顶部下拉框代表当前正在配置的Collection,通过下拉菜单可以切换。 -- 配置完成后,点击"Run query"可以通过配置获取数据,"Data"面板会展示数据。 - -#### 度量 - - - -度量字段,通常是图表需要展示的核心数据。度量数据可以通过聚合函数进行统计,支持常用的数据库统计函数`Sum`, `Count`, `Avg`, `Max`, `Min`. 度量字段可以有多个,可以设置别名。 - -#### 维度 - - - -维度字段,通常是图表数据分组的依据。对于日期类型字段,支持如图所示的格式化方式,格式化通过数据库函数实现(例如:MySQL对应`date_format`),其他类型数据格式化见[数据转换](数据转换)部分。 - -> **维度格式化 (Dimensions Format) VS 数据转换 (Transform)** -> - 维度格式化发生在获取最终数据之前,数据分组按照维度格式化后的值进行,通常在按时间段筛选数据时有此需求。 -> - 数据转换对响应数据做进一步处理,诸如可读性处理,以展现恰当的数据,数据转换在前端进行。 - -#### 筛选 - - - -此处配置将对分组前的数据进行过滤。 - -#### 排序 (Sort) 和限制 (Limit) - - - -目前图表允许的数据集条数上限为2000. - -#### 缓存 - - - -开启缓存后,图表将展示缓存的数据。 - -### 图表配置 - - - -- 图表类型 (Chart Type) - 用于展示的图表类型,目前按图表库分组。如何使用其他图表库? -- 基础配置 - 选择图表后,会出现相应的基础可视化配置,字段配置通常提供了下拉菜单供选择,选项中包含了Collection的基础字段和字段别名。 -- JSON配置 - 当基础配置不满足要求时,可以使用JSON配置其他图表属性。 - -### 数据转换 - - - -使用数据转换可以对接口响应的数据做进一步处理,目前支持转换处理的数据类型为 `number`, `date`, `time`, `datetime`, 对于不属于支持的数据类型的字段,可以手动选择为这几个类型,以使用对应的转换方法。 - -## 使用其他图表库 - -```TypeScript -import { ChartLibraryProvider } from '@nocobase/plugin-charts-v2/client'; -``` - -图表插件提供了ChartLibraryProvider组件,组件接收以下属性: -- name 图表库名字 -- charts 图表组件列表,参考`packages/plugins/charts-v2/src/client/renderer/library/G2PlotLibrary.tsx` \ No newline at end of file +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-data-visualization/package.json b/packages/plugins/@nocobase/plugin-data-visualization/package.json index ee1af88378..4a7b7333b2 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/package.json +++ b/packages/plugins/@nocobase/plugin-data-visualization/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-data-visualization", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "Data visualization", "displayName.zh-CN": "数据可视化", "description": "Provides data visualization feature, including chart block and chart filter block, support line charts, area charts, bar charts and more than a dozen kinds of charts, you can also extend more chart types.", diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/CardItem.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/CardItem.tsx index df09cd5788..c741507912 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/CardItem.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/CardItem.tsx @@ -15,6 +15,7 @@ export const ChartCardItem = withDynamicSchemaProps( const { token } = useToken(); return ( { + const { [yField]: y } = data; + const yFieldProps = fieldProps[yField]; + const name = yFieldProps?.label || yField; + const value = yFieldProps?.transformer ? yFieldProps.transformer(y) : y; + return { + name, + value, + }; + }, + ], + }, colorField: () => { const props = fieldProps[yField]; return props?.label || yField; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-data-visualization/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-data-visualization/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-data-visualization/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-data-visualization/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-data-visualization/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-data-visualization/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/api.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/api.test.ts index 63423950cd..2cd38aa693 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/api.test.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/api.test.ts @@ -10,7 +10,8 @@ import { Database, Repository } from '@nocobase/database'; import { MockServer, createMockServer } from '@nocobase/test'; import compose from 'koa-compose'; -import { parseBuilder, parseFieldAndAssociations, queryData } from '../actions/query'; +import { parseFieldAndAssociations, queryData } from '../actions/query'; +import { createQueryParser } from '../query-parser'; describe('api', () => { let app: MockServer; @@ -91,7 +92,8 @@ describe('api', () => { }, }, } as any; - await compose([parseFieldAndAssociations, parseBuilder, queryData])(ctx, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse(), queryData])(ctx, async () => {}); expect(ctx.action.params.values.data).toBeDefined(); }); @@ -125,7 +127,8 @@ describe('api', () => { }, }, } as any; - await compose([parseFieldAndAssociations, parseBuilder, queryData])(ctx, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse(), queryData])(ctx, async () => {}); expect(ctx.action.params.values.data).toBeDefined(); expect(ctx.action.params.values.data).toMatchObject([{ createdAt: '2023-01' }, { createdAt: '2023-02' }]); }); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/formatter.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/formatter.test.ts index f87283e133..0230c0f74c 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/formatter.test.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/formatter.test.ts @@ -10,7 +10,8 @@ import { Database } from '@nocobase/database'; import { MockServer, createMockServer } from '@nocobase/test'; import compose from 'koa-compose'; -import { parseBuilder, parseFieldAndAssociations, queryData } from '../actions/query'; +import { parseFieldAndAssociations, queryData } from '../actions/query'; +import { createQueryParser } from '../query-parser'; describe('formatter', () => { let app: MockServer; @@ -85,7 +86,8 @@ describe('formatter', () => { }, }, } as any; - await compose([parseFieldAndAssociations, parseBuilder, queryData])(ctx, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse(), queryData])(ctx, async () => {}); expect(ctx.action.params.values.data).toBeDefined(); expect(ctx.action.params.values.data).toMatchObject([{ date: '2024-05-15 01:02:30' }]); }); @@ -125,7 +127,8 @@ describe('formatter', () => { }, }, } as any; - await compose([parseFieldAndAssociations, parseBuilder, queryData])(ctx, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse(), queryData])(ctx, async () => {}); expect(ctx.action.params.values.data).toBeDefined(); expect(ctx.action.params.values.data).toMatchObject([{ dateOnly: '2024-05-14' }]); }); @@ -165,7 +168,8 @@ describe('formatter', () => { }, }, } as any; - await compose([parseFieldAndAssociations, parseBuilder, queryData])(ctx, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse(), queryData])(ctx, async () => {}); expect(ctx.action.params.values.data).toBeDefined(); expect(ctx.action.params.values.data).toMatchObject([{ datetimeNoTz: '2024-05-14 19:32:30' }]); }); @@ -213,7 +217,8 @@ describe('formatter', () => { }, }, } as any; - await compose([parseFieldAndAssociations, parseBuilder, queryData])(ctx, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse(), queryData])(ctx, async () => {}); expect(ctx.action.params.values.data).toBeDefined(); expect(ctx.action.params.values.data).toMatchObject([{ unixTs: '2023-01-01 10:04:56' }]); }); @@ -260,7 +265,8 @@ describe('formatter', () => { }, }, } as any; - await compose([parseFieldAndAssociations, parseBuilder, queryData])(ctx, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse(), queryData])(ctx, async () => {}); expect(ctx.action.params.values.data).toBeDefined(); expect(ctx.action.params.values.data).toMatchObject([{ unixTsMs: '2023-01-01 10:04:56' }]); }); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/query.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/query.test.ts index 51a39314cc..2c4070679c 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/query.test.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/__tests__/query.test.ts @@ -13,13 +13,14 @@ import { vi } from 'vitest'; import { cacheMiddleware, checkPermission, - parseBuilder, parseFieldAndAssociations, parseVariables, postProcess, } from '../actions/query'; import { Database } from '@nocobase/database'; import * as formatter from '../formatter'; +import { createQueryParser } from '../query-parser'; +import { QueryParser } from '../query-parser/query-parser'; describe('query', () => { describe('parseBuilder', () => { @@ -157,7 +158,8 @@ describe('query', () => { }, }, }; - await compose([parseFieldAndAssociations, parseBuilder])(context, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse()])(context, async () => {}); expect(context.action.params.values.queryParams.attributes).toEqual([ [db.sequelize.col('orders.price'), 'price'], ]); @@ -179,7 +181,7 @@ describe('query', () => { }, }, }; - await compose([parseFieldAndAssociations, parseBuilder])(context2, async () => {}); + await compose([parseFieldAndAssociations, queryParser.parse()])(context2, async () => {}); expect(context2.action.params.values.queryParams.attributes).toEqual([ [db.sequelize.fn('sum', db.sequelize.col('orders.price')), 'price-alias'], ]); @@ -203,20 +205,17 @@ describe('query', () => { }, }, }; + const queryParser = createQueryParser(db); try { - await compose([parseFieldAndAssociations, parseBuilder])(context, async () => {}); + await compose([parseFieldAndAssociations, queryParser.parse()])(context, async () => {}); } catch (error) { expect(error.message).toBe('Invalid aggregation function: if(1=2,sleep(1),sleep(3)) and sum'); } }); it('should parse dimensions', async () => { - vi.spyOn(formatter, 'createFormatter').mockImplementation( - () => - ({ - format: () => 'formatted-field' as any, - }) as any, - ); + const queryParser = createQueryParser(db); + vi.spyOn(queryParser.formatter, 'format').mockImplementation(() => 'formatted-field' as any); const dimensions = [ { field: ['createdAt'], @@ -235,7 +234,7 @@ describe('query', () => { }, }, }; - await compose([parseFieldAndAssociations, parseBuilder])(context, async () => {}); + await compose([parseFieldAndAssociations, queryParser.parse()])(context, async () => {}); expect(context.action.params.values.queryParams.attributes).toEqual([['formatted-field', 'Created at']]); expect(context.action.params.values.queryParams.group).toEqual([]); const measures = [ @@ -256,7 +255,7 @@ describe('query', () => { }, }, }; - await compose([parseFieldAndAssociations, parseBuilder])(context2, async () => {}); + await compose([parseFieldAndAssociations, queryParser.parse()])(context2, async () => {}); expect(context2.action.params.values.queryParams.group).toEqual(['formatted-field']); }); @@ -277,7 +276,8 @@ describe('query', () => { }, }, }; - await compose([parseFieldAndAssociations, parseBuilder])(context, async () => {}); + const queryParser = createQueryParser(db); + await compose([parseFieldAndAssociations, queryParser.parse()])(context, async () => {}); expect(context.action.params.values.queryParams.where.createdAt).toBeDefined(); }); @@ -332,7 +332,6 @@ describe('query', () => { await parseVariables(context, async () => {}); const { filter } = context.action.params.values; const dateOn = filter.$and[0].createdAt.$dateOn; - console.log(dateOn); expect(new Date(dateOn).getTime()).toBeLessThanOrEqual(new Date().getTime()); const userId = filter.$and[1].userId.$eq; expect(userId).toBe(1); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts index 2770dd48d7..631937a59a 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/actions/query.ts @@ -12,52 +12,8 @@ import { BelongsToArrayAssociation, Field, FilterParser } from '@nocobase/databa import compose from 'koa-compose'; import { Cache } from '@nocobase/cache'; import { middlewares } from '@nocobase/server'; -import { createFormatter } from '../formatter'; - -type MeasureProps = { - field: string | string[]; - type?: string; - aggregation?: string; - alias?: string; - distinct?: boolean; -}; - -type DimensionProps = { - field: string | string[]; - type?: string; - alias?: string; - format?: string; - options?: any; -}; - -type OrderProps = { - field: string | string[]; - alias?: string; - order?: 'asc' | 'desc'; -}; - -type QueryParams = Partial<{ - uid: string; - dataSource: string; - collection: string; - measures: MeasureProps[]; - dimensions: DimensionProps[]; - orders: OrderProps[]; - filter: any; - limit: number; - sql: { - fields?: string; - clauses?: string; - }; - cache: { - enabled: boolean; - ttl: number; - }; - // Get the latest data from the database - refresh: boolean; -}>; - -const AllowedAggFuncs = ['sum', 'count', 'avg', 'min', 'max']; +import { QueryParams } from '../types'; +import { createQueryParser } from '../query-parser'; const getDB = (ctx: Context, dataSource: string) => { const ds = ctx.app.dataSourceManager.dataSources.get(dataSource); @@ -109,79 +65,6 @@ export const queryData = async (ctx: Context, next: Next) => { // return data; }; -export const parseBuilder = async (ctx: Context, next: Next) => { - const { dataSource, measures, dimensions, orders, include, where, limit } = ctx.action.params.values; - const db = getDB(ctx, dataSource) || ctx.db; - const { sequelize } = db; - const attributes = []; - const group = []; - const order = []; - const fieldMap = {}; - let hasAgg = false; - - measures.forEach((measure: MeasureProps & { field: string }) => { - const { field, aggregation, alias, distinct } = measure; - const attribute = []; - const col = sequelize.col(field); - if (aggregation) { - if (!AllowedAggFuncs.includes(aggregation)) { - throw new Error(`Invalid aggregation function: ${aggregation}`); - } - hasAgg = true; - attribute.push(sequelize.fn(aggregation, distinct ? sequelize.fn('DISTINCT', col) : col)); - } else { - attribute.push(col); - } - if (alias) { - attribute.push(alias); - } - attributes.push(attribute.length > 1 ? attribute : attribute[0]); - fieldMap[alias || field] = measure; - }); - - dimensions.forEach((dimension: DimensionProps & { field: string }) => { - const { field, format, alias, type, options } = dimension; - const attribute = []; - const col = sequelize.col(field); - if (format) { - const formatter = createFormatter(sequelize); - attribute.push(formatter.format({ type, field, format, timezone: ctx.timezone, options })); - } else { - attribute.push(col); - } - if (alias) { - attribute.push(alias); - } - attributes.push(attribute.length > 1 ? attribute : attribute[0]); - if (hasAgg) { - group.push(attribute[0]); - } - fieldMap[alias || field] = dimension; - }); - - orders.forEach((item: OrderProps) => { - const alias = sequelize.getQueryInterface().quoteIdentifier(item.alias); - const name = hasAgg ? sequelize.literal(alias) : sequelize.col(item.field as string); - order.push([name, item.order || 'ASC']); - }); - - ctx.action.params.values = { - ...ctx.action.params.values, - queryParams: { - where, - attributes, - include, - group, - order, - limit: limit || 2000, - subQuery: false, - raw: true, - }, - fieldMap, - }; - await next(); -}; - export const parseFieldAndAssociations = async (ctx: Context, next: Next) => { const { dataSource, @@ -323,13 +206,16 @@ export const checkPermission = (ctx: Context, next: Next) => { }; export const query = async (ctx: Context, next: Next) => { + const { dataSource } = ctx.action.params.values as QueryParams; + const db = getDB(ctx, dataSource) || ctx.db; + const queryParser = createQueryParser(db); try { await compose([ checkPermission, cacheMiddleware, parseVariables, parseFieldAndAssociations, - parseBuilder, + queryParser.parse(), queryData, postProcess, ])(ctx, next); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/formatter/index.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/formatter/index.ts deleted file mode 100644 index 3644ba183e..0000000000 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/formatter/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { Sequelize } from 'sequelize'; -import { SQLiteFormatter } from './sqlite-formatter'; -import { PostgresFormatter } from './postgres-formatter'; -import { MySQLFormatter } from './mysql-formatter'; - -export const createFormatter = (sequelize: Sequelize) => { - const dialect = sequelize.getDialect(); - switch (dialect) { - case 'sqlite': - return new SQLiteFormatter(sequelize); - case 'postgres': - return new PostgresFormatter(sequelize); - case 'mysql': - case 'mariadb': - return new MySQLFormatter(sequelize); - } -}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/formatter/oracle-formatter.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/formatter/oracle-formatter.ts new file mode 100644 index 0000000000..a60e163b04 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/formatter/oracle-formatter.ts @@ -0,0 +1,55 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Col, Formatter } from './formatter'; + +export class OracleFormatter extends Formatter { + convertFormat(format: string) { + return format.replace(/hh/g, 'HH24').replace(/mm/g, 'MI').replace(/ss/g, 'SS'); + } + formatDate(field: Col, format: string, timezoneOffset?: string) { + format = this.convertFormat(format); + const timezone = this.getTimezoneByOffset(timezoneOffset); + if (timezone) { + const col = this.sequelize.getQueryInterface().quoteIdentifiers((field as Col).col); + const fieldWithTZ = this.sequelize.literal(`(${col} AT TIME ZONE '${timezone}')`); + return this.sequelize.fn('to_char', fieldWithTZ, format); + } + return this.sequelize.fn('to_char', field, format); + } + + formatUnixTimeStamp( + field: string, + format: string, + accuracy: 'second' | 'millisecond' = 'second', + timezoneOffset?: string, + ) { + format = this.convertFormat(format); + const col = this.sequelize.getQueryInterface().quoteIdentifiers(field); + const timezone = this.getTimezoneByOffset(timezoneOffset); + if (timezone) { + if (accuracy === 'millisecond') { + return this.sequelize.fn( + 'to_char', + this.sequelize.literal(`to_timestamp(ROUND(${col} / 1000)) AT TIME ZONE '${timezone}'`), + format, + ); + } + return this.sequelize.fn( + 'to_char', + this.sequelize.literal(`to_timestamp(${col}) AT TIME ZONE '${timezone}'`), + format, + ); + } + if (accuracy === 'millisecond') { + return this.sequelize.fn('to_char', this.sequelize.literal(`to_timestamp(ROUND(${col} / 1000)`), format); + } + return this.sequelize.fn('to_char', this.sequelize.literal(`to_timestamp(${col})`), format); + } +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts index 6732980ecd..285ec787dc 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts @@ -10,7 +10,6 @@ import { Cache } from '@nocobase/cache'; import { InstallOptions, Plugin } from '@nocobase/server'; import { query } from './actions/query'; -import { resolve } from 'path'; export class PluginDataVisualizationServer extends Plugin { cache: Cache; @@ -18,7 +17,7 @@ export class PluginDataVisualizationServer extends Plugin { afterAdd() {} beforeLoad() { - this.app.resource({ + this.app.resourceManager.define({ name: 'charts', actions: { query, diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/index.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/index.ts new file mode 100644 index 0000000000..a5c2a8e0c1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/index.ts @@ -0,0 +1,32 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Database } from '@nocobase/database'; +import { SQLiteQueryParser } from './sqlite-query-parser'; +import { PostgresQueryParser } from './postgres-query-parser'; +import { MySQLQueryParser } from './mysql-query-parser'; +import { QueryParser } from './query-parser'; +import { OracleQueryParser } from './oracle-query-parser'; + +export const createQueryParser = (db: Database) => { + const dialect = db.sequelize.getDialect(); + switch (dialect) { + case 'sqlite': + return new SQLiteQueryParser(db); + case 'postgres': + return new PostgresQueryParser(db); + case 'mysql': + case 'mariadb': + return new MySQLQueryParser(db); + case 'oracle': + return new OracleQueryParser(db); + default: + return new QueryParser(db); + } +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/mysql-query-parser.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/mysql-query-parser.ts new file mode 100644 index 0000000000..3845fe078f --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/mysql-query-parser.ts @@ -0,0 +1,21 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Database } from '@nocobase/database'; +import { MySQLFormatter } from '../formatter/mysql-formatter'; +import { QueryParser } from './query-parser'; + +export class MySQLQueryParser extends QueryParser { + declare formatter: MySQLFormatter; + + constructor(db: Database) { + super(db); + this.formatter = new MySQLFormatter(db.sequelize); + } +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/oracle-query-parser.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/oracle-query-parser.ts new file mode 100644 index 0000000000..f03922af91 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/oracle-query-parser.ts @@ -0,0 +1,41 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { QueryParser } from './query-parser'; +import { OrderProps, QueryParams } from '../types'; +import { Context } from '@nocobase/actions'; +import { OracleFormatter } from '../formatter/oracle-formatter'; +import { Database } from '@nocobase/database'; + +export class OracleQueryParser extends QueryParser { + declare formatter: OracleFormatter; + + constructor(db: Database) { + super(db); + this.formatter = new OracleFormatter(db.sequelize); + } + + parseOrders(ctx: Context, orders: OrderProps[], hasAgg: boolean) { + const { collection: collectionName, dimensions } = ctx.action.params.values as QueryParams; + const collection = this.db.getCollection(collectionName); + if (!orders.length) { + if (dimensions.length) { + orders.push(dimensions[0]); + } else { + let filterTks = collection.filterTargetKey; + if (!Array.isArray(filterTks)) { + filterTks = [filterTks]; + } + orders.push(...filterTks.map((field) => ({ field, alias: field }))); + } + } + const order = super.parseOrders(ctx, orders, hasAgg); + return order; + } +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/postgres-query-parser.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/postgres-query-parser.ts new file mode 100644 index 0000000000..3a77daefcb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/postgres-query-parser.ts @@ -0,0 +1,21 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Database } from '@nocobase/database'; +import { PostgresFormatter } from '../formatter/postgres-formatter'; +import { QueryParser } from './query-parser'; + +export class PostgresQueryParser extends QueryParser { + declare formatter: PostgresFormatter; + + constructor(db: Database) { + super(db); + this.formatter = new PostgresFormatter(db.sequelize); + } +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/query-parser.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/query-parser.ts new file mode 100644 index 0000000000..a593591b76 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/query-parser.ts @@ -0,0 +1,120 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Context, Next } from '@nocobase/actions'; +import { DimensionProps, MeasureProps, OrderProps } from '../types'; +import { Formatter } from '../formatter/formatter'; +import { Database } from '@nocobase/database'; + +const AllowedAggFuncs = ['sum', 'count', 'avg', 'min', 'max']; + +export class QueryParser { + db: Database; + formatter: Formatter; + + constructor(db: Database) { + this.db = db; + this.formatter = { + format: ({ field }) => db.sequelize.col(field), + } as Formatter; + } + + parseMeasures(ctx: Context, measures: MeasureProps[]) { + let hasAgg = false; + const sequelize = this.db.sequelize; + const attributes = []; + const fieldMap = {}; + measures.forEach((measure: MeasureProps & { field: string }) => { + const { field, aggregation, alias, distinct } = measure; + const attribute = []; + const col = sequelize.col(field); + if (aggregation) { + if (!AllowedAggFuncs.includes(aggregation)) { + throw new Error(`Invalid aggregation function: ${aggregation}`); + } + hasAgg = true; + attribute.push(sequelize.fn(aggregation, distinct ? sequelize.fn('DISTINCT', col) : col)); + } else { + attribute.push(col); + } + if (alias) { + attribute.push(alias); + } + attributes.push(attribute.length > 1 ? attribute : attribute[0]); + fieldMap[alias || field] = measure; + }); + return { attributes, fieldMap, hasAgg }; + } + + parseDimensions(ctx: Context, dimensions: (DimensionProps & { field: string })[], hasAgg: boolean, timezone: string) { + const sequelize = this.db.sequelize; + const attributes = []; + const group = []; + const fieldMap = {}; + dimensions.forEach((dimension: DimensionProps & { field: string }) => { + const { field, format, alias, type, options } = dimension; + const attribute = []; + const col = sequelize.col(field); + if (format) { + attribute.push(this.formatter.format({ type, field, format, timezone, options })); + } else { + attribute.push(col); + } + if (alias) { + attribute.push(alias); + } + attributes.push(attribute.length > 1 ? attribute : attribute[0]); + if (hasAgg) { + group.push(attribute[0]); + } + fieldMap[alias || field] = dimension; + }); + return { attributes, group, fieldMap }; + } + + parseOrders(ctx: Context, orders: OrderProps[], hasAgg: boolean) { + const sequelize = this.db.sequelize; + const order = []; + orders.forEach((item: OrderProps) => { + const alias = sequelize.getQueryInterface().quoteIdentifier(item.alias); + const name = hasAgg ? sequelize.literal(alias) : sequelize.col(item.field as string); + order.push([name, item.order || 'ASC']); + }); + return order; + } + + parse() { + return async (ctx: Context, next: Next) => { + const { measures, dimensions, orders, include, where, limit } = ctx.action.params.values; + const { attributes: measureAttributes, fieldMap: measureFieldMap, hasAgg } = this.parseMeasures(ctx, measures); + const { + attributes: dimensionAttributes, + group, + fieldMap: dimensionFieldMap, + } = this.parseDimensions(ctx, dimensions, hasAgg, ctx.timezone); + const order = this.parseOrders(ctx, orders, hasAgg); + + ctx.action.params.values = { + ...ctx.action.params.values, + queryParams: { + where, + attributes: [...measureAttributes, ...dimensionAttributes], + include, + group, + order, + limit: limit || 2000, + subQuery: false, + raw: true, + }, + fieldMap: { ...measureFieldMap, ...dimensionFieldMap }, + }; + await next(); + }; + } +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/sqlite-query-parser.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/sqlite-query-parser.ts new file mode 100644 index 0000000000..872a7153b8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/query-parser/sqlite-query-parser.ts @@ -0,0 +1,21 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Database } from '@nocobase/database'; +import { SQLiteFormatter } from '../formatter/sqlite-formatter'; +import { QueryParser } from './query-parser'; + +export class SQLiteQueryParser extends QueryParser { + declare formatter: SQLiteFormatter; + + constructor(db: Database) { + super(db); + this.formatter = new SQLiteFormatter(db.sequelize); + } +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/types.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/types.ts new file mode 100644 index 0000000000..7aff7351d6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/types.ts @@ -0,0 +1,51 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export type MeasureProps = { + field: string | string[]; + type?: string; + aggregation?: string; + alias?: string; + distinct?: boolean; +}; + +export type DimensionProps = { + field: string | string[]; + type?: string; + alias?: string; + format?: string; + options?: any; +}; + +export type OrderProps = { + field: string | string[]; + alias?: string; + order?: 'asc' | 'desc'; +}; + +export type QueryParams = Partial<{ + uid: string; + dataSource: string; + collection: string; + measures: MeasureProps[]; + dimensions: DimensionProps[]; + orders: OrderProps[]; + filter: any; + limit: number; + sql: { + fields?: string; + clauses?: string; + }; + cache: { + enabled: boolean; + ttl: number; + }; + // Get the latest data from the database + refresh: boolean; +}>; diff --git a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json index b1f6d352ae..ee9fbdf7b6 100644 --- a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json +++ b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-disable-pm-add", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "./dist/server/index.js", "peerDependencies": { "@nocobase/client": "1.x", diff --git a/packages/plugins/@nocobase/plugin-error-handler/README.md b/packages/plugins/@nocobase/plugin-error-handler/README.md index 5a9532ed61..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/README.md +++ b/packages/plugins/@nocobase/plugin-error-handler/README.md @@ -1,9 +1,30 @@ -# error-handler +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-error-handler/README.zh-CN.md b/packages/plugins/@nocobase/plugin-error-handler/README.zh-CN.md deleted file mode 100644 index 1537b48996..0000000000 --- a/packages/plugins/@nocobase/plugin-error-handler/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# error-handler - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-error-handler/package.json b/packages/plugins/@nocobase/plugin-error-handler/package.json index 2ec5237125..3a0cb9d98d 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/package.json +++ b/packages/plugins/@nocobase/plugin-error-handler/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "错误处理器", "description": "Handling application errors and exceptions.", "description.zh-CN": "处理应用程序中的错误和异常。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-error-handler/src/locale/en-US.json new file mode 100644 index 0000000000..3601fa9dd1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-error-handler/src/locale/en-US.json @@ -0,0 +1,6 @@ +{ + "unique violation": "{{field}} already exists", + "notNull violation": "{{field}} cannot be null", + "Validation error": "{{field}} validation error", + "notNull Violation": "{{field}} cannot be null" +} diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/fr_FR.json b/packages/plugins/@nocobase/plugin-error-handler/src/locale/fr-FR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-error-handler/src/locale/fr_FR.json rename to packages/plugins/@nocobase/plugin-error-handler/src/locale/fr-FR.json diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-error-handler/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-error-handler/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-error-handler/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-error-handler/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-error-handler/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-error-handler/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh-CN.json new file mode 100644 index 0000000000..7bb4234aca --- /dev/null +++ b/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh-CN.json @@ -0,0 +1,5 @@ +{ + "unique violation": "{{field}} 字段值已存在", + "notNull violation": "{{field}} 字段不能为空", + "Validation error": "{{field}} 字段规则验证失败" +} diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/__tests__/render-error.test.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/__tests__/render-error.test.ts index b83f1a6bf8..46bc9c7d8c 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/__tests__/render-error.test.ts +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/__tests__/render-error.test.ts @@ -112,13 +112,7 @@ describe('create with exception', () => { expect(response.statusCode).toEqual(400); - expect(response.body).toEqual({ - errors: [ - { - message: 'name must be unique', - }, - ], - }); + expect(response.body['errors'][0]['message']).toBe('name already exists'); }); it('should render error with field title', async () => { @@ -174,7 +168,7 @@ describe('create with exception', () => { const db: Database = ctx.db; const sql = `INSERT INTO ${userCollection.model.tableName} (name) - VALUES (:name)`; + VALUES (:name)`; await db.sequelize.query(sql, { replacements: { name: ctx.action.params.values.name }, diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/en_US.json b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/en-US.json similarity index 99% rename from packages/plugins/@nocobase/plugin-error-handler/src/locale/en_US.json rename to packages/plugins/@nocobase/plugin-error-handler/src/server/locale/en-US.json index 1d03ff8807..a3a19db5c5 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/src/locale/en_US.json +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/en-US.json @@ -3,4 +3,4 @@ "notNull violation": "notNull violation", "Validation error": "{{field}} validation error", "notNull Violation": "{{field}} cannot be null" -} +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/en_US.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/en_US.ts deleted file mode 100644 index 6c0cf9c4d1..0000000000 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/en_US.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'unique violation': '{{field}} must be unique', - 'notNull violation': 'notNull violation', - 'Validation error': '{{field}} validation error', - 'notNull Violation': '{{field}} cannot be null', -}; diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/es-ES.json b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/es-ES.json new file mode 100644 index 0000000000..c44267c321 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/es-ES.json @@ -0,0 +1,6 @@ +{ + "unique violation": "{{field}} debe ser único", + "notNull violation": "notNull violación", + "Validation error": "{{field}} error de validación", + "notNull Violation": "{{field}} no puede ser null" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/es-ES.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/es-ES.ts deleted file mode 100644 index c98a843845..0000000000 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/es-ES.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - "unique violation": "{{field}} debe ser único", - "notNull violation": "notNull violación", - "Validation error": "{{field}} error de validación", - "notNull Violation": "{{field}} no puede ser null" -}; diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/fr-FR.json b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/fr-FR.json new file mode 100644 index 0000000000..9e3488eb2c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/fr-FR.json @@ -0,0 +1,6 @@ +{ + "unique violation": "{{field}} doit être unique", + "notNull violation": "Violation de contrainte notNull", + "Validation error": "Erreur de validation de {{field}}", + "notNull Violation": "{{field}} ne peut pas être null" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/fr_FR.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/fr_FR.ts deleted file mode 100644 index fc54c4461a..0000000000 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/fr_FR.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'unique violation': '{{field}} doit être unique', - 'notNull violation': 'Violation de contrainte notNull', - 'Validation error': 'Erreur de validation de {{field}}', - 'notNull Violation': '{{field}} ne peut pas être null', -}; diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/ja-JP.json new file mode 100644 index 0000000000..7edf2da364 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/ja-JP.json @@ -0,0 +1,4 @@ +{ + "unique violation": "{{field}} は一意でなくてはなりません", + "notNull Violation": "{{field}} はNullにできません" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/pt-BR.json b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/pt-BR.json new file mode 100644 index 0000000000..58153683cc --- /dev/null +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/pt-BR.json @@ -0,0 +1,6 @@ +{ + "unique violation": "{{field}} deve ser único", + "notNull violation": "violação de não nulo", + "Validation error": "erro de validação de {{field}}", + "notNull Violation": "{{field}} não pode ser nulo" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/pt-BR.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/pt-BR.ts deleted file mode 100644 index eedf896c63..0000000000 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/pt-BR.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'unique violation': '{{field}} deve ser único', - 'notNull violation': 'violação de não nulo', - 'Validation error': 'erro de validação de {{field}}', - 'notNull Violation': '{{field}} não pode ser nulo', -}; diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh_CN.json b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/zh-CN.json similarity index 98% rename from packages/plugins/@nocobase/plugin-error-handler/src/locale/zh_CN.json rename to packages/plugins/@nocobase/plugin-error-handler/src/server/locale/zh-CN.json index 057a84fbca..5da9dd66e3 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/src/locale/zh_CN.json +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/zh-CN.json @@ -2,4 +2,4 @@ "unique violation": "{{field}} 字段值是唯一的", "notNull violation": "{{field}} 字段不能为空", "Validation error": "{{field}} 字段规则验证失败" -} +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/zh_CN.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/zh_CN.ts deleted file mode 100644 index 05f5f8d907..0000000000 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/locale/zh_CN.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - 'unique violation': '{{field}} 字段值是唯一的', - 'notNull violation': '{{field}} 字段不能为空', - 'Validation error': '{{field}} 字段规则验证失败', -}; diff --git a/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts b/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts index 2c6e38c1ce..5fd05fa7cf 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-error-handler/src/server/server.ts @@ -12,8 +12,6 @@ import { BaseError } from '@nocobase/database'; import { Plugin } from '@nocobase/server'; import lodash from 'lodash'; import { ErrorHandler } from './error-handler'; -import enUS from './locale/en_US'; -import zhCN from './locale/zh_CN'; export class PluginErrorHandlerServer extends Plugin { errorHandler: ErrorHandler = new ErrorHandler(); @@ -56,7 +54,7 @@ export class PluginErrorHandlerServer extends Plugin { return { message: t(err.type, { ns: this.i18nNs, - field: t(title, { ns: 'lm-collections' }), + field: t(title, { ns: ['lm-collections', 'client'] }), }), }; }), @@ -67,8 +65,6 @@ export class PluginErrorHandlerServer extends Plugin { } async load() { - this.app.i18n.addResources('zh-CN', this.i18nNs, zhCN); - this.app.i18n.addResources('en-US', this.i18nNs, enUS); - this.app.use(this.errorHandler.middleware(), { before: 'cors', tag: 'errorHandler' }); + this.app.use(this.errorHandler.middleware(), { after: 'i18n', tag: 'errorHandler', before: 'cors' }); } } diff --git a/packages/plugins/@nocobase/plugin-field-china-region/README.md b/packages/plugins/@nocobase/plugin-field-china-region/README.md index e762c6d8d6..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-field-china-region/README.md +++ b/packages/plugins/@nocobase/plugin-field-china-region/README.md @@ -1,11 +1,30 @@ -# china-region +# NocoBase -English | [中文](./README.zh-CN.md) + -中国行政区划插件。 -## 安装激活 +## What is NocoBase -内置插件无需手动安装激活。 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 使用方法 +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-china-region/README.zh-CN.md b/packages/plugins/@nocobase/plugin-field-china-region/README.zh-CN.md deleted file mode 100644 index 9180fab21f..0000000000 --- a/packages/plugins/@nocobase/plugin-field-china-region/README.zh-CN.md +++ /dev/null @@ -1,11 +0,0 @@ -# china-region - -[English](./README.md) | 中文 - -中国行政区划插件。 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-field-china-region/package.json b/packages/plugins/@nocobase/plugin-field-china-region/package.json index dd7846d4d8..7831380c18 100644 --- a/packages/plugins/@nocobase/plugin-field-china-region/package.json +++ b/packages/plugins/@nocobase/plugin-field-china-region/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-field-china-region", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "Administrative divisions of China", "displayName.zh-CN": "中国行政区划", "description": "Provides data and field type for administrative divisions of China.", diff --git a/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts b/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts index 652a51db02..78407f53d9 100644 --- a/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts +++ b/packages/plugins/@nocobase/plugin-field-china-region/src/server/index.ts @@ -30,7 +30,7 @@ export class PluginFieldChinaRegionServer extends Plugin { this.app.acl.allow('chinaRegions', 'list', 'loggedIn'); - this.app.resourcer.use(async (ctx, next) => { + this.app.resourceManager.use(async function blockChinaRegionList(ctx, next) { const { resourceName, actionName } = ctx.action.params; if (resourceName == 'chinaRegions' && actionName !== 'list') { diff --git a/packages/plugins/@nocobase/plugin-field-formula/README.md b/packages/plugins/@nocobase/plugin-field-formula/README.md index a954e99b5d..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-field-formula/README.md +++ b/packages/plugins/@nocobase/plugin-field-formula/README.md @@ -1,9 +1,30 @@ -# Formula field +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-formula/README.zh-CN.md b/packages/plugins/@nocobase/plugin-field-formula/README.zh-CN.md deleted file mode 100644 index 9efe631f84..0000000000 --- a/packages/plugins/@nocobase/plugin-field-formula/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# 公式字段 - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-field-formula/package.json b/packages/plugins/@nocobase/plugin-field-formula/package.json index c1bee2cc8f..f934ac78f7 100644 --- a/packages/plugins/@nocobase/plugin-field-formula/package.json +++ b/packages/plugins/@nocobase/plugin-field-formula/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表字段:公式", "description": "Configure and store the results of calculations between multiple field values in the same record, supporting both Math.js and Excel formula functions.", "description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/field-formula", diff --git a/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx b/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx index 52a88dc8da..7921af6f54 100644 --- a/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx +++ b/packages/plugins/@nocobase/plugin-field-formula/src/client/components/Formula/Result.tsx @@ -61,6 +61,21 @@ function getValuesByPath(values, key, index?) { } } +function getValuesByFullPath(values, fieldPath) { + const fieldPaths = fieldPath.split('.'); + let currentKeyIndex = 0; + let value = values; + //loop to get the last field + while (currentKeyIndex < fieldPaths.length) { + const fieldName = fieldPaths[currentKeyIndex]; + const index = parseInt(fieldPaths?.[currentKeyIndex + 1]); + value = getValuesByPath(value, fieldName, index); + //have index means an array, then jump 2; else 1 + currentKeyIndex = currentKeyIndex + (index >= 0 ? 2 : 1); + } + return value; +} + function areValuesEqual(value1, value2) { if (_.isString(value1) && !isNaN(Date.parse(value1))) { value1 = new Date(value1); @@ -87,8 +102,7 @@ export function Result(props) { const field: any = useField(); const path: any = field.path.entire; const fieldPath = path?.replace(`.${fieldSchema.name}`, ''); - const fieldName = fieldPath.split('.')[0]; - const index = parseInt(fieldPath.split('.')?.[1]); + useEffect(() => { setEditingValue(value); }, [value]); @@ -103,7 +117,9 @@ export function Result(props) { ) { return; } - const scope = toJS(getValuesByPath(form.values, fieldName, index)); + //field name may be like todos.0.sub_todos.0.title + //scope should be the deepest one + const scope = toJS(getValuesByFullPath(form.values, fieldPath)); let v; try { v = evaluate(expression, scope); diff --git a/packages/plugins/@nocobase/plugin-field-formula/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-field-formula/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-field-formula/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-field-formula/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-field-formula/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-field-formula/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-field-formula/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-field-formula/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/README.md b/packages/plugins/@nocobase/plugin-field-m2m-array/README.md index cb1506ac29..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-field-m2m-array/README.md +++ b/packages/plugins/@nocobase/plugin-field-m2m-array/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-field-record-set +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/package.json b/packages/plugins/@nocobase/plugin-field-m2m-array/package.json index 150fb553f7..b30eed6dae 100644 --- a/packages/plugins/@nocobase/plugin-field-m2m-array/package.json +++ b/packages/plugins/@nocobase/plugin-field-m2m-array/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表字段:多对多 (数组)", "description": "Allows to create many to many relationships between two models by storing an array of unique keys of the target model.", "description.zh-CN": "支持通过在数组中存储目标表唯一键的方式建立多对多关系。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "peerDependencies": { "@nocobase/client": "1.x", diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/src/client/mbm.ts b/packages/plugins/@nocobase/plugin-field-m2m-array/src/client/mbm.ts index 5e9acd0bda..4da690a3e7 100644 --- a/packages/plugins/@nocobase/plugin-field-m2m-array/src/client/mbm.ts +++ b/packages/plugins/@nocobase/plugin-field-m2m-array/src/client/mbm.ts @@ -8,14 +8,10 @@ */ import { ISchema } from '@formily/react'; -import { Collection, CollectionFieldInterface } from '@nocobase/client'; +import { CollectionFieldInterface, getUniqueKeyFromCollection } from '@nocobase/client'; import { tval } from '@nocobase/utils/client'; import { NAMESPACE } from './locale'; -function getUniqueKeyFromCollection(collection: Collection) { - return collection?.filterTargetKey?.[0] || collection?.filterTargetKey || collection?.getPrimaryKey() || 'id'; -} - export class MBMFieldInterface extends CollectionFieldInterface { name = 'mbm'; type = 'object'; @@ -104,7 +100,7 @@ export class MBMFieldInterface extends CollectionFieldInterface { type: 'string', title: '{{t("Target collection")}}', required: true, - 'x-reactions': ['{{useAsyncDataSource(loadCollections, ["file"])}}'], + 'x-reactions': ['{{useAsyncDataSource(loadCollections)}}'], 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-disabled': '{{ !createOnly }}', diff --git a/packages/plugins/@nocobase/plugin-field-m2m-array/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-field-m2m-array/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-field-m2m-array/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-field-m2m-array/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/README.md b/packages/plugins/@nocobase/plugin-field-markdown-vditor/README.md index c186374aee..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/README.md +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-field-markdown-vditor +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json index bc3b98ee84..2ad9341c3f 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表字段:Markdown(Vditor)", "description": "Used to store Markdown and render it using Vditor editor, supports common Markdown syntax such as list, code, quote, etc., and supports uploading images, recordings, etc.It also allows for instant rendering, where what you see is what you get.", "description.zh-CN": "用于存储 Markdown,并使用 Vditor 编辑器渲染,支持常见 Markdown 语法,如列表,代码,引用等,并支持上传图片,录音等。同时可以做到即时渲染,所见即所得。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/field-markdown-vditor", diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.ts b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.json similarity index 74% rename from packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.ts rename to packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.json index 93effc0a46..2c71874069 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.ts +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.json @@ -1,13 +1,4 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { +{ "Vditor": "Markdown(Vditor)", "File collection": "File collection", "Used to store files uploaded in the Markdown editor": "Used to store files uploaded in the Markdown editor", @@ -39,4 +30,4 @@ export default { "Preview": "Preview", "Fullscreen": "Toggle Fullscreen", "Outline": "Outline" -} +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja_JP.ts b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja-JP.json similarity index 63% rename from packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja_JP.ts rename to packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja-JP.json index 7f163c0f7a..e41532b670 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja_JP.ts +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja-JP.json @@ -1,22 +1,4 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { +{ "Vditor": "Markdown(Vditor)", "File collection": "ファイルコレクション", "Used to store files uploaded in the Markdown editor": "Markdownエディタでアップロードされたファイルを保存するために使用", @@ -47,5 +29,5 @@ export default { "Both": "編集とプレビュー", "Preview": "プレビュー", "Fullscreen": "全画面表示の切り替え", - "Outline": "アウトライン", -}; \ No newline at end of file + "Outline": "アウトライン" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko_KR.ts b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko-KR.json similarity index 77% rename from packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko_KR.ts rename to packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko-KR.json index d91dec0d02..ebae584d99 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko_KR.ts +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko-KR.json @@ -1,13 +1,4 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { +{ "Vditor": "Markdown(Vditor)", "File collection": "파일 데이터 테이블", "Used to store files uploaded in the Markdown editor": "Markdown 편집기에 업로드된 파일을 저장하는 데 사용됩니다", @@ -39,4 +30,4 @@ export default { "Preview": "미리보기", "Fullscreen": "전체화면", "Outline": "개요" -} +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.json similarity index 75% rename from packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.ts rename to packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.json index 950f83117e..d2bc2eb6b1 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.ts +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.json @@ -1,13 +1,4 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { +{ "Vditor": "Markdown(Vditor)", "File collection": "文件数据表", "Used to store files uploaded in the Markdown editor": "用于存储在Markdown编辑器中上传的文件", @@ -39,4 +30,4 @@ export default { "Preview": "预览", "Fullscreen": "全屏切换", "Outline": "大纲" -} +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-sequence/README.md b/packages/plugins/@nocobase/plugin-field-sequence/README.md index 5631580273..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-field-sequence/README.md +++ b/packages/plugins/@nocobase/plugin-field-sequence/README.md @@ -1,9 +1,30 @@ -# sequence-field +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-field-sequence/README.zh-CN.md b/packages/plugins/@nocobase/plugin-field-sequence/README.zh-CN.md deleted file mode 100644 index 97ab1000d5..0000000000 --- a/packages/plugins/@nocobase/plugin-field-sequence/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# sequence-field - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-field-sequence/package.json b/packages/plugins/@nocobase/plugin-field-sequence/package.json index 43d0a5e7ce..dbcd482ac2 100644 --- a/packages/plugins/@nocobase/plugin-field-sequence/package.json +++ b/packages/plugins/@nocobase/plugin-field-sequence/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表字段:自动编码", "description": "Automatically generate codes based on configured rules, supporting combinations of dates, numbers, and text.", "description.zh-CN": "根据配置的规则自动生成编码,支持日期、数字、文本的组合。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/field-sequence", diff --git a/packages/plugins/@nocobase/plugin-field-sequence/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-field-sequence/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-field-sequence/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-field-sequence/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-field-sequence/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-field-sequence/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-field-sequence/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-field-sequence/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-field-sort/package.json b/packages/plugins/@nocobase/plugin-field-sort/package.json index 29fd0cca19..f5ccff5f12 100644 --- a/packages/plugins/@nocobase/plugin-field-sort/package.json +++ b/packages/plugins/@nocobase/plugin-field-sort/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-field-sort", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "dependencies": {}, "peerDependencies": { diff --git a/packages/plugins/@nocobase/plugin-file-manager/README.md b/packages/plugins/@nocobase/plugin-file-manager/README.md index 36ca419eb7..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/README.md +++ b/packages/plugins/@nocobase/plugin-file-manager/README.md @@ -1,9 +1,30 @@ -# file-manager +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -内置插件无需手动安装激活。 +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-file-manager/README.zh-CN.md b/packages/plugins/@nocobase/plugin-file-manager/README.zh-CN.md deleted file mode 100644 index 4fe1a5a487..0000000000 --- a/packages/plugins/@nocobase/plugin-file-manager/README.zh-CN.md +++ /dev/null @@ -1,9 +0,0 @@ -# file-manager - -[English](./README.md) | 中文 - -## 安装激活 - -内置插件无需手动安装激活。 - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-file-manager/package.json b/packages/plugins/@nocobase/plugin-file-manager/package.json index e040c4edd8..d6efe46b3e 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/package.json +++ b/packages/plugins/@nocobase/plugin-file-manager/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-file-manager", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "File manager", "displayName.zh-CN": "文件管理器", "description": "Provides files storage services with files collection template and attachment field.", diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/createLocalStorage.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/createLocalStorage.test.ts index 4793cde5ae..5387da40f9 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/createLocalStorage.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/createLocalStorage.test.ts @@ -22,7 +22,6 @@ test.describe('file manager', () => { // 2、测试步骤:进入“文件管理器”-“新建”按钮,填写表单,点击“确定”按钮 await page.goto('/admin/settings/file-manager'); - await page.waitForLoadState('networkidle'); await page.getByRole('button', { name: 'plus Add new' }).hover(); await page.getByRole('menuitem', { name: 'Local storage' }).click(); const createLocalStorage = new CreateLocalStorage(page); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/editLocalStorage.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/editLocalStorage.test.ts index ee4a1a8e62..e2fc154ec3 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/editLocalStorage.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/__e2e__/editLocalStorage.test.ts @@ -20,7 +20,7 @@ test.describe('File manager', () => { // 1、前置条件:1.1已登录;1.2存在一个文件管理器 await page.goto('/admin/settings/file-manager'); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); await page.getByRole('button', { name: 'plus Add new' }).hover(); await page.getByRole('menuitem', { name: 'Local storage' }).click(); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts index 5ffbcd6f05..e40f1cb927 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageRules.ts @@ -7,18 +7,30 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useCollectionField, useCollectionManager, useRequest } from '@nocobase/client'; +import { useEffect } from 'react'; +import { useField } from '@formily/react'; +import { useAPIClient, useCollectionField, useCollectionManager, useRequest } from '@nocobase/client'; export function useStorageRules(storage) { const name = storage ?? ''; - const { loading, data } = useRequest( + const apiClient = useAPIClient(); + const field = useField(); + const { loading, data, run } = useRequest( { url: `storages:getRules/${name}`, }, { + manual: true, refreshDeps: [name], + cacheKey: name, }, ); + useEffect(() => { + if (field.pattern !== 'editable') { + return; + } + run(); + }, [field.pattern, run]); return (!loading && data?.data) || null; } diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts index 689f352d9a..76467da549 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useUploadFiles.ts @@ -12,26 +12,29 @@ import { useActionContext, useBlockRequestContext, useCollection, + useDataBlockProps, + useDataBlockRequest, + useSourceId, useSourceIdFromParentRecord, } from '@nocobase/client'; import { useContext, useMemo } from 'react'; import { useStorageRules } from './useStorageRules'; export const useUploadFiles = () => { - const { service } = useBlockRequestContext(); + const service = useDataBlockRequest(); + const { association } = useDataBlockProps(); const { setVisible } = useActionContext(); - const { props: blockProps } = useBlockRequestContext(); const collection = useCollection(); - const sourceId = useSourceIdFromParentRecord(); + const sourceId = useSourceId(); const rules = useStorageRules(collection?.getOption('storage')); const action = useMemo(() => { let action = `${collection.name}:create`; - if (blockProps?.association) { - const [s, t] = blockProps.association.split('.'); + if (association) { + const [s, t] = association.split('.'); action = `${s}/${sourceId}/${t}:create`; } return action; - }, [collection.name, blockProps?.association, sourceId]); + }, [collection.name, association, sourceId]); const { setSelectedRows } = useContext(RecordPickerContext) || {}; const uploadingFiles = {}; diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts index 7d06ed0ca0..6b9e36b082 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts @@ -157,12 +157,24 @@ export class FileCollectionTemplate extends CollectionTemplate { }, ...getConfigurableProperties('category', 'description'), storage: { - title: `{{t("File storage", { ns: "${NAMESPACE}" })}}`, - type: 'hasOne', + type: 'string', name: 'storage', + title: `{{t("File storage", { ns: "${NAMESPACE}" })}}`, 'x-decorator': 'FormItem', - 'x-component': 'Select', - 'x-reactions': ['{{useAsyncDataSource(loadStorages)}}'], + 'x-component': 'RemoteSelect', + 'x-component-props': { + service: { + resource: 'storages', + params: { + // pageSize: -1 + }, + }, + manual: false, + fieldNames: { + label: 'title', + value: 'name', + }, + }, }, ...getConfigurableProperties('presetFields'), }; diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json index bfead819d9..aadd212423 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json +++ b/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json @@ -2,7 +2,6 @@ "File manager": "ファイルストレージ", "Attachment": "添付ファイル", "MIME type": "ファイル形式", - "Allow uploading multiple files": "複数ファイルのアップロードを許可する", "Storage display name": "ファイルストレージ名", "Storage name": "ファイルストレージ識別子", "Storage type": "ストレージタイプ", @@ -14,5 +13,29 @@ "Aliyun OSS": "Aliyun OSS", "Tencent COS": "Tencent COS", "Amazon S3": "Amazon S3", - "Filename": "ファイル名" + "Region": "地域", + "Bucket": "バケット", + "Path": "パス", + "Filename": "ファイル名", + "See more": "続きを見る", + "Will be used for API": "API で使用されます", + "File collection": "ファイルデータテーブル", + "File name": "ファイル名", + "Extension name": "拡張子", + "Size": "ファイルサイズ", + "File size limit": "ファイルサイズ制限", + "Minimum from 1 byte, maximum up to 1GB.": "最小サイズは1バイト、最大サイズは1GBです。", + "File type (in MIME type format)": "ファイルタイプ(MIME形式)", + "Multi-types seperated with comma, for example: \"image/*\", \"image/png\", \"image/*, application/pdf\" etc.": "複数のタイプはカンマで区切ります。 例えば「image/*」「image/png」「image/*、application/pdf」など。", + "URL": "URL", + "File storage": "ファイルストレージ", + "Allow uploading multiple files": "複数ファイルのアップロードを許可する", + "Storage": "ストレージ", + "Storages": "ストレージ", + "Access base URL": "アクセスURLのベース", + "Base URL for file access, could be your CDN base URL. For example: \"https://cdn.nocobase.com\".": "术语调整,“基礎URL”改为“ベースURL”", + "Relative path the file will be saved to. Left blank as root path. The leading and trailing slashes \"/\" will be ignored. For example: \"user/avatar\".": "更清晰的表达方式,符合技术描述", + "Default storage will be used when not selected": "空欄の場合はデフォルトのストレージが使用されます。", + "Keep file in storage when destroy record": "レコード削除時にファイルを保持", + "Aliyun OSS region part of the bucket. For example: \"oss-cn-beijing\".": "阿里クラウドOSSの地域。 例えば「oss-cn-beijing」。" } diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja_JP.json deleted file mode 100644 index 5e1dd1a01b..0000000000 --- a/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja_JP.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "File manager": "ファイルストレージ", - "Attachment": "添付ファイル", - "MIME type": "ファイル形式", - "Storage display name": "ファイルストレージ名", - "Storage name": "ファイルストレージ識別子", - "Storage type": "ストレージタイプ", - "Default storage": "デフォルトストレージ", - "Storage base URL": "Storage base URL", - "Destination": "ファイルパス", - "Use the built-in static file server": "組み込みの静的ファイル サービスを使用する", - "Local storage": "ローカルストレージ", - "Aliyun OSS": "Aliyun OSS", - "Tencent COS": "Tencent COS", - "Amazon S3": "Amazon S3", - "Region": "地域", - "Bucket": "バケット", - "Path": "パス", - "Filename": "ファイル名", - "See more": "続きを見る", - "Will be used for API": "API で使用されます", - "File collection": "ファイルデータテーブル", - "File name": "ファイル名", - "Extension name": "拡張子", - "Size": "ファイルサイズ", - "File size limit": "ファイルサイズ制限", - "Minimum from 1 byte, maximum up to 1GB.": "最小サイズは1バイト、最大サイズは1GBです。", - "File type (in MIME type format)": "ファイルタイプ(MIME形式)", - "Multi-types seperated with comma, for example: \"image/*\", \"image/png\", \"image/*, application/pdf\" etc.": "複数のタイプはカンマで区切ります。 例えば「image/*」「image/png」「image/*、application/pdf」など。", - "URL": "URL", - "File storage": "ファイルストレージ", - "Allow uploading multiple files": "複数ファイルのアップロードを許可", - "Storage": "ストレージ", - "Storages": "ストレージ", - "Access base URL": "アクセスURLのベース", - "Base URL for file access, could be your CDN base URL. For example: \"https://cdn.nocobase.com\".": "术语调整,“基礎URL”改为“ベースURL”", - "Relative path the file will be saved to. Left blank as root path. The leading and trailing slashes \"/\" will be ignored. For example: \"user/avatar\".": "更清晰的表达方式,符合技术描述", - "Default storage will be used when not selected": "空欄の場合はデフォルトのストレージが使用されます。", - "Keep file in storage when destroy record": "レコード削除時にファイルを保持", - "Aliyun OSS region part of the bucket. For example: \"oss-cn-beijing\".": "阿里クラウドOSSの地域。 例えば「oss-cn-beijing」。" -} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-file-manager/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-file-manager/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-file-manager/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts index b458fa1619..21aed67841 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/action.test.ts @@ -457,6 +457,26 @@ describe('action', () => { }); }); + describe('association', () => { + it('has-many', async () => { + const UserRepo = db.getRepository('users'); + const user = await UserRepo.findOne(); + const FileRepo = db.getRepository('users.files', user.id); + const f1s = await FileRepo.count(); + expect(f1s).toBe(0); + const { body } = await agent.resource('users.files', 1).create({ + [FILE_FIELD_NAME]: path.resolve(__dirname, './files/text.txt'), + }); + const f2s = await FileRepo.find({}); + expect(f2s.length).toBe(1); + expect(f2s[0].userId).toBe(user.id); + + await agent.resource('users.files', 1).destroy({ filterByTk: body.data.id }); + const f3s = await FileRepo.count(); + expect(f3s).toBe(0); + }); + }); + describe('storage actions', () => { describe('getRules', () => { it('get rules without key as default storage', async () => { diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/collections/files.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/collections/files.ts new file mode 100644 index 0000000000..d240920e17 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/collections/files.ts @@ -0,0 +1,148 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export default { + name: 'files', + createdBy: true, + updatedBy: true, + template: 'file', + fields: [ + { + interface: 'input', + type: 'string', + name: 'title', + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("Title")}}`, + 'x-component': 'Input', + }, + }, + // '系统文件名(含扩展名)', + { + interface: 'input', + type: 'string', + name: 'filename', + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("File name")}}`, + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + // '扩展名(含“.”)', + { + interface: 'input', + type: 'string', + name: 'extname', + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("Extension name")}}`, + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + // '文件体积(字节)', + { + interface: 'integer', + type: 'integer', + name: 'size', + deletable: false, + uiSchema: { + type: 'number', + title: `{{t("Size")}}`, + 'x-component': 'InputNumber', + 'x-read-pretty': true, + 'x-component-props': { + stringMode: true, + step: '0', + }, + }, + }, + { + interface: 'input', + type: 'string', + name: 'mimetype', + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("MIME type")}}`, + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + // '相对路径(含“/”前缀)', + { + interface: 'input', + type: 'string', + name: 'path', + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("Path")}}`, + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + // 文件的可访问地址 + { + interface: 'url', + type: 'string', + name: 'url', + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("URL")}}`, + 'x-component': 'Input.URL', + 'x-read-pretty': true, + }, + }, + // 用于预览 + { + interface: 'url', + type: 'string', + name: 'preview', + field: 'url', // 直接引用 url 字段 + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("Preview")}}`, + 'x-component': 'Preview', + 'x-read-pretty': true, + }, + }, + { + comment: '存储引擎', + type: 'belongsTo', + name: 'storage', + target: 'storages', + foreignKey: 'storageId', + deletable: false, + uiSchema: { + type: 'string', + title: `{{t("Storage")}}`, + 'x-component': 'Input', + 'x-read-pretty': true, + }, + }, + // '其他文件信息(如图片的宽高)', + { + type: 'jsonb', + name: 'meta', + deletable: false, + defaultValue: {}, + }, + { + type: 'belongsTo', + name: 'user', + }, + ], +}; diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/tables/users.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/collections/users.ts similarity index 92% rename from packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/tables/users.ts rename to packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/collections/users.ts index afdb4cfe71..197fdd068f 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/tables/users.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/collections/users.ts @@ -21,6 +21,10 @@ export default { name: 'avatar', target: 'attachments', }, + { + type: 'hasMany', + name: 'files', + }, { type: 'belongsToMany', name: 'pubkeys', diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/index.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/index.ts index 50d3c82073..769ec5e7a1 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/index.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/__tests__/index.ts @@ -30,7 +30,7 @@ export async function getApp(options = {}): Promise { }); await app.db.import({ - directory: path.resolve(__dirname, './tables'), + directory: path.resolve(__dirname, './collections'), }); await app.db.sync(); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts index b757236179..a3925eb9cc 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/attachments.ts @@ -131,14 +131,14 @@ export async function createMiddleware(ctx: Context, next: Next) { } export async function destroyMiddleware(ctx: Context, next: Next) { - const { resourceName, actionName } = ctx.action; + const { resourceName, actionName, sourceId } = ctx.action; const collection = ctx.db.getCollection(resourceName); if (collection?.options?.template !== 'file' || actionName !== 'destroy') { return next(); } - const repository = ctx.db.getRepository(resourceName); + const repository = ctx.db.getRepository(resourceName, sourceId); const { filterByTk, filter } = ctx.action.params; diff --git a/packages/plugins/@nocobase/plugin-gantt/README.md b/packages/plugins/@nocobase/plugin-gantt/README.md index 849ea6026a..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-gantt/README.md +++ b/packages/plugins/@nocobase/plugin-gantt/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-gantt +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-gantt/package.json b/packages/plugins/@nocobase/plugin-gantt/package.json index c727dc0393..7d3f3712bd 100644 --- a/packages/plugins/@nocobase/plugin-gantt/package.json +++ b/packages/plugins/@nocobase/plugin-gantt/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-gantt", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "displayName": "Block: Gantt", "displayName.zh-CN": "区块:甘特图", "description": "Provides Gantt block.", @@ -10,7 +10,7 @@ "homepage": "https://docs.nocobase.com/handbook/block-gantt", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-gantt", "devDependencies": { - "antd-style": "3.4.5" + "antd-style": "3.7.1" }, "peerDependencies": { "@nocobase/client": "1.x", diff --git a/packages/plugins/@nocobase/plugin-gantt/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-gantt/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-gantt/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-gantt/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-gantt/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-gantt/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-gantt/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-gantt/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/README.md b/packages/plugins/@nocobase/plugin-graph-collection-manager/README.md index 9dc8a57f15..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/README.md +++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/README.md @@ -1,11 +1,30 @@ -# graph-collection-manager +# NocoBase -English | [中文](./README.zh-CN.md) + -## 安装激活 -```bash -yarn pm enable graph-collection-manager -``` +## What is NocoBase -## 使用方法 +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/README.zh-CN.md b/packages/plugins/@nocobase/plugin-graph-collection-manager/README.zh-CN.md deleted file mode 100644 index e36cca20a3..0000000000 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/README.zh-CN.md +++ /dev/null @@ -1,11 +0,0 @@ -# graph-collection-manager - -[English](./README.md) | 中文 - -## 安装激活 - -```bash -yarn pm enable graph-collection-manager -``` - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json index cdaffd49fb..61c89f99fb 100644 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json +++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "可视化数据表管理", "description": "An ER diagram-like tool. Currently only the Master database is supported.", "description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/graph-collection-manager", diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx index 22e9d40ba3..59db3761ca 100644 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx +++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/GraphDrawPage.tsx @@ -20,7 +20,6 @@ import { APIClientProvider, ApplicationContext, CollectionCategoriesContext, - CollectionCategoriesProvider, CurrentAppInfoContext, DataSourceApplicationProvider, SchemaComponent, @@ -38,7 +37,7 @@ import { import { App, Button, ConfigProvider, Layout, Spin, Switch, Tooltip } from 'antd'; import dagre from 'dagre'; import lodash from 'lodash'; -import React, { createContext, forwardRef, useContext, useEffect, useLayoutEffect, useState } from 'react'; +import React, { createContext, forwardRef, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useAsyncDataSource, useCreateActionAndRefreshCM } from './action-hooks'; import { AddCollectionAction } from './components/AddCollectionAction'; @@ -384,13 +383,15 @@ export const GraphDrawPage = React.memo(() => { const [collectionList, setCollectionList] = useState([]); const [loading, setLoading] = useState(false); const { collections, getCollections } = useCollectionManager_deprecated(); + const categoryCtx = useContext(CollectionCategoriesContext); + const categoryCtxRef = useRef(); + categoryCtxRef.current = categoryCtx; const dm = useDataSourceManager(); const currentAppInfo = useCurrentAppInfo(); const app = useApp(); const { data: { database }, } = currentAppInfo; - const categoryCtx = useContext(CollectionCategoriesContext); const scope = { ...options?.scope }; const components = { ...options?.components }; const saveGraphPositionAction = async (data) => { @@ -442,7 +443,6 @@ export const GraphDrawPage = React.memo(() => { }; const dataSource = useDataSource(); - const initGraphCollections = () => { targetGraph = new Graph({ container: document.getElementById('container')!, @@ -520,7 +520,7 @@ export const GraphDrawPage = React.memo(() => { - + {/* TODO: 因为画布中的卡片是一次性注册进 Graph 的,这里的 theme 是存在闭包里的,因此当主题动态变更时,并不会触发卡片的重新渲染 */}
@@ -531,7 +531,7 @@ export const GraphDrawPage = React.memo(() => {
-
+
diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx index 6597f054d2..06c518059c 100644 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx +++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/client/components/Entity.tsx @@ -420,7 +420,7 @@ const Entity: React.FC<{ ); diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ja-JP.json index c155f3912a..39f39fc0ae 100644 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ja-JP.json +++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ja-JP.json @@ -5,9 +5,13 @@ "Collection Search": "表フィルタ", "Create Collection": "データテーブルの作成", "All Fields": "すべて", - "Associations Fields": "関係フィールド", + "Association Fields": "関連フィールド", + "Choices fields": "選択フィールド", "All relationships": "すべての関係", "Entity relationship only": "エンティティ関係", "Inheritance relationship only": "継承関係", - "Selection": "せんたく" + "Graphical interface": "グラフィカルインターフェース", + "Selection": "せんたく", + "Auto layout": "自動レイアウト", + "Associations Fields": "関係フィールド" } diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ja_JP.json deleted file mode 100644 index f4fb4f289a..0000000000 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ja_JP.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Graph Collection": "グラフコレクション", - "Collection List": "コレクションリスト", - "Full Screen": "全画面", - "Collection Search": "コレクション検索", - "Create Collection": "コレクションを作成", - "All Fields": "全フィールド", - "Association Fields": "関連フィールド", - "Choices fields": "選択フィールド", - "All relationships": "全ての関係", - "Entity relationship only": "エンティティ関係のみ", - "Inheritance relationship only": "継承関係のみ", - "Graphical interface": "グラフィカルインターフェース", - "Selection": "選択モード", - "Auto layout": "自動レイアウト", - "Associations Fields": "関連フィールド" -} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-graph-collection-manager/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-kanban/README.md b/packages/plugins/@nocobase/plugin-kanban/README.md index 8cfc6ddeff..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-kanban/README.md +++ b/packages/plugins/@nocobase/plugin-kanban/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-kanban +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-kanban/package.json b/packages/plugins/@nocobase/plugin-kanban/package.json index 76dd671ae2..df583b331f 100644 --- a/packages/plugins/@nocobase/plugin-kanban/package.json +++ b/packages/plugins/@nocobase/plugin-kanban/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-kanban", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/block-kanban", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban", diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx index 948335e436..d554caef17 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Card.tsx @@ -10,10 +10,11 @@ import { css } from '@emotion/css'; import { FormLayout } from '@formily/antd-v5'; import { createForm } from '@formily/core'; -import { observer, RecursionField, useFieldSchema } from '@formily/react'; +import { RecursionField, useFieldSchema } from '@formily/react'; import { DndContext, FormProvider, + getCardItemSchema, PopupContextProvider, useCollection, useCollectionRecordData, @@ -67,86 +68,95 @@ const cardCss = css` const MemorizedRecursionField = React.memo(RecursionField); MemorizedRecursionField.displayName = 'MemorizedRecursionField'; -export const KanbanCard: any = observer( - () => { - const collection = useCollection(); - const { setDisableCardDrag } = useContext(KanbanCardContext) || {}; - const fieldSchema = useFieldSchema(); - const { openPopup, getPopupSchemaFromSchema } = usePopupUtils(); - const recordData = useCollectionRecordData(); - const popupSchema = getPopupSchemaFromSchema(fieldSchema) || getPopupSchemaFromParent(fieldSchema); - const [visible, setVisible] = useState(false); - const { isPopupVisibleControlledByURL } = usePopupSettings(); - const handleCardClick = useCallback( - (e: React.MouseEvent) => { - const targetElement = e.target as Element; // 将事件目标转换为Element类型 - const currentTargetElement = e.currentTarget as Element; - if (currentTargetElement.contains(targetElement)) { - if (!isPopupVisibleControlledByURL()) { - setVisible(true); - } else { - openPopup({ - popupUidUsedInURL: popupSchema?.['x-uid'], - }); - } - e.stopPropagation(); +export const KanbanCard: any = () => { + const collection = useCollection(); + const { setDisableCardDrag } = useContext(KanbanCardContext) || {}; + const fieldSchema = useFieldSchema(); + const { openPopup, getPopupSchemaFromSchema } = usePopupUtils(); + const recordData = useCollectionRecordData(); + const popupSchema = getPopupSchemaFromSchema(fieldSchema) || getPopupSchemaFromParent(fieldSchema); + const [visible, setVisible] = useState(false); + const { isPopupVisibleControlledByURL } = usePopupSettings(); + const handleCardClick = useCallback( + (e: React.MouseEvent) => { + const targetElement = e.target as Element; // 将事件目标转换为Element类型 + const currentTargetElement = e.currentTarget as Element; + if (currentTargetElement.contains(targetElement)) { + if (!isPopupVisibleControlledByURL()) { + setVisible(true); } else { - e.stopPropagation(); + openPopup({ + popupUidUsedInURL: popupSchema?.['x-uid'], + }); } + e.stopPropagation(); + } else { + e.stopPropagation(); + } + }, + [openPopup, popupSchema], + ); + const cardStyle = useMemo(() => { + return { + cursor: 'pointer', + overflow: 'hidden', + }; + }, []); + + const form = useMemo(() => { + return createForm({ + values: recordData, + }); + }, [recordData]); + + const onDragStart = useCallback(() => { + setDisableCardDrag?.(true); + }, [setDisableCardDrag]); + const onDragEnd = useCallback(() => { + setDisableCardDrag?.(false); + }, [setDisableCardDrag]); + + // if not wrapped, only Tab component's content will be rendered, Drawer component's content will not be rendered + const wrappedPopupSchema = useMemo(() => { + return { + type: 'void', + properties: { + drawer: popupSchema, }, - [openPopup, popupSchema], - ); - const cardStyle = useMemo(() => { - return { - cursor: 'pointer', - overflow: 'hidden', - }; - }, []); + }; + }, [popupSchema]); + const cardItemSchema = getCardItemSchema?.(fieldSchema); + const { + layout = 'vertical', + labelAlign = 'left', + labelWidth = 120, + labelWrap = true, + } = cardItemSchema?.['x-component-props'] || {}; - const form = useMemo(() => { - return createForm({ - values: recordData, - }); - }, [recordData]); - - const onDragStart = useCallback(() => { - setDisableCardDrag?.(true); - }, [setDisableCardDrag]); - const onDragEnd = useCallback(() => { - setDisableCardDrag?.(false); - }, [setDisableCardDrag]); - - // if not wrapped, only Tab component's content will be rendered, Drawer component's content will not be rendered - const wrappedPopupSchema = useMemo(() => { - return { - type: 'void', - properties: { - drawer: popupSchema, - }, - }; - }, [popupSchema]); - - return ( - <> - - - - - - - - - - - - - - - - ); - }, - { displayName: 'KanbanCard' }, -); + return ( + <> + + + + + + + + + + + + + + + + ); +}; function getPopupSchemaFromParent(fieldSchema: Schema) { if (fieldSchema.parent?.properties?.cardViewer?.properties?.drawer) { diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Settings.tsx b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Settings.tsx index 02d76b72f6..aa6473c554 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Settings.tsx +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/Kanban.Settings.tsx @@ -20,8 +20,11 @@ import { useCollection_deprecated, useDesignable, useFormBlockContext, + SchemaSettingsLayoutItem, } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; import { useKanbanBlockContext } from './KanbanBlockProvider'; + export const kanbanSettings = new SchemaSettings({ name: 'blockSettings:kanban', items: [ @@ -76,7 +79,36 @@ export const kanbanSettings = new SchemaSettings({ }; }, }, - + { + name: 'allowDragAndDrop', + type: 'switch', + useComponentProps: () => { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { t } = useTranslation(); + const { dn } = useDesignable(); + return { + title: t('Enable drag and drop sorting'), + checked: field.componentProps?.dragSort !== false, + onChange: async (dragSort) => { + field.componentProps = field.componentProps || {}; + field.componentProps.dragSort = dragSort; + fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'].dragSort = dragSort; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-component-props': fieldSchema['x-component-props'], + }, + }); + }, + }; + }, + }, + { + name: 'setBlockLayout', + Component: SchemaSettingsLayoutItem, + }, { name: 'divider', type: 'divider', diff --git a/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockProvider.tsx b/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockProvider.tsx index 401925eed9..78208677dd 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockProvider.tsx +++ b/packages/plugins/@nocobase/plugin-kanban/src/client/KanbanBlockProvider.tsx @@ -8,7 +8,7 @@ */ import { ArrayField } from '@formily/core'; -import { useField } from '@formily/react'; +import { useField, useFieldSchema } from '@formily/react'; import { Spin } from 'antd'; import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; import { @@ -82,8 +82,13 @@ export const useKanbanBlockContext = () => { }; const useDisableCardDrag = () => { + const fieldSchema = useFieldSchema(); + const { dragSort } = fieldSchema?.parent?.['x-component-props'] || {}; const ctx = useKanbanBlockContext(); const { allowAll, allowConfigure, parseAction } = useACLRoleContext(); + if (dragSort === false) { + return true; + } if (allowAll || allowConfigure) { return false; } diff --git a/packages/plugins/@nocobase/plugin-kanban/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-kanban/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-kanban/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-kanban/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-kanban/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-kanban/src/locale/zh-CN.json index d3e67103fc..51ca237620 100644 --- a/packages/plugins/@nocobase/plugin-kanban/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-kanban/src/locale/zh-CN.json @@ -4,5 +4,6 @@ "Create sort field": "创建排序字段", "Convert the following integer fields to sorting fields": "将以下整数字段转为排序字段", "Sorting field":"排序字段", - "Grouped sorting based on":"基于分组字段" + "Grouped sorting based on":"基于分组字段", + "Enable drag and drop sorting":"启用拖拽排序" } diff --git a/packages/plugins/@nocobase/plugin-localization/README.md b/packages/plugins/@nocobase/plugin-localization/README.md index 07f5ef46cf..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-localization/README.md +++ b/packages/plugins/@nocobase/plugin-localization/README.md @@ -1,35 +1,30 @@ -# Localization Management +# NocoBase -支持管理应用程序的多语言资源。 - -## 使用方法 - -### 在`系统设置`中添加对应语言 - - - -### 切换到对应语言 - - - -### 管理多语言资源 - -1. 同步需要翻译的原文内容 - - - -- 目前支持的内容 - - 菜单 - - 系统和插件提供的语言包 - - 数据表名、字段名、字段选项标签 - -> **Note** -> 新增菜单、数据表名、字段名、字段选项标签会自动同步 -> 已有内容需要点击同步按钮手动同步 - -2. 编辑翻译内容,点击`发布`按钮即可生效 - - + - \ No newline at end of file +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-localization/package.json b/packages/plugins/@nocobase/plugin-localization/package.json index e9193d9170..df1ceee944 100644 --- a/packages/plugins/@nocobase/plugin-localization/package.json +++ b/packages/plugins/@nocobase/plugin-localization/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-localization", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/localization-management", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/localization-management", diff --git a/packages/plugins/@nocobase/plugin-localization/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-localization/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-localization/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-localization/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-localization/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-localization/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-localization/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-localization/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-localization/src/server/__tests__/actions.test.ts b/packages/plugins/@nocobase/plugin-localization/src/server/__tests__/actions.test.ts index 5742932503..1f5b7fe04c 100644 --- a/packages/plugins/@nocobase/plugin-localization/src/server/__tests__/actions.test.ts +++ b/packages/plugins/@nocobase/plugin-localization/src/server/__tests__/actions.test.ts @@ -27,9 +27,11 @@ describe('actions', () => { }; beforeAll(async () => { + process.env.APP_ENV = 'production'; app = await createMockServer({ plugins: ['localization'], }); + await app.emitAsync('afterLoad'); db = app.db; repo = db.getRepository('localizationTexts'); agent = app.agent(); @@ -106,5 +108,28 @@ describe('actions', () => { expect(res.body.data[0].translation).toBeUndefined(); }); }); + + it('publish', async () => { + await repo.create({ + values: [ + { + module: 'test', + text: 'text', + translations: [ + { + locale: 'en-US', + translation: 'translation', + }, + ], + }, + ], + }); + const { resources } = await app.localeManager.get('en-US'); + expect(resources.test).toBeUndefined(); + await agent.resource('localization').publish(); + const { resources: resources2 } = await app.localeManager.get('en-US'); + expect(resources2.test).toBeDefined(); + expect(resources2.test.text).toBe('translation'); + }); }); }); diff --git a/packages/plugins/@nocobase/plugin-localization/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-localization/src/server/plugin.ts index 5bfa09fcdb..b2d4fc722b 100644 --- a/packages/plugins/@nocobase/plugin-localization/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-localization/src/server/plugin.ts @@ -118,6 +118,7 @@ export class PluginLocalizationServer extends Plugin { this.app.localeManager.registerResourceStorer('plugin-localization', { getResources: (lang: string) => this.resources.getResources(lang), + reset: () => this.resources.reset(), }); } diff --git a/packages/plugins/@nocobase/plugin-localization/src/server/resources.ts b/packages/plugins/@nocobase/plugin-localization/src/server/resources.ts index bef751b3c5..949829454b 100644 --- a/packages/plugins/@nocobase/plugin-localization/src/server/resources.ts +++ b/packages/plugins/@nocobase/plugin-localization/src/server/resources.ts @@ -83,7 +83,7 @@ export default class Resources { await this.cache.set(`texts`, [...existTexts, ...newTexts]); } - async resetCache(locale: string) { - await this.cache.del(`translations:${locale}`); + async reset() { + await this.cache.reset(); } } diff --git a/packages/plugins/@nocobase/plugin-logger/README.md b/packages/plugins/@nocobase/plugin-logger/README.md index f0953db11c..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-logger/README.md +++ b/packages/plugins/@nocobase/plugin-logger/README.md @@ -1 +1,30 @@ -# @nocobase/plugin-logger +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-logger/package.json b/packages/plugins/@nocobase/plugin-logger/package.json index b4430b410e..ca33c09824 100644 --- a/packages/plugins/@nocobase/plugin-logger/package.json +++ b/packages/plugins/@nocobase/plugin-logger/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "日志", "description": "Server-side logs, mainly including API request logs and system runtime logs, and allows to package and download log files.", "description.zh-CN": "服务端日志,主要包括接口请求日志和系统运行日志,并支持打包和下载日志文件。", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/logger", diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-logger/src/locale/en-US.json new file mode 100644 index 0000000000..c2dff23273 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-logger/src/locale/en-US.json @@ -0,0 +1,9 @@ +{ + "Logger": "Logger", + "Search": "Search", + "Download": "Download", + "Download logs": "Download logs", + "API request and response logs": "API request and response logs", + "Application, database, plugins and other system logs, the error level logs will be sent to": "Application, database, plugins and other system logs, the error level logs will be sent to", + "SQL execution logs, printed by Sequelize when the db logging is enabled": "SQL execution logs, printed by Sequelize when the db logging is enabled" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/en-US.ts b/packages/plugins/@nocobase/plugin-logger/src/locale/en-US.ts deleted file mode 100644 index 64d70dab40..0000000000 --- a/packages/plugins/@nocobase/plugin-logger/src/locale/en-US.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Logger: 'Logger', - Search: 'Search', - Download: 'Download', - 'Download logs': 'Download logs', - 'API request and response logs': 'API request and response logs', - 'Application, database, plugins and other system logs, the error level logs will be sent to': - 'Application, database, plugins and other system logs, the error level logs will be sent to', - 'SQL execution logs, printed by Sequelize when the db logging is enabled': - 'SQL execution logs, printed by Sequelize when the db logging is enabled', -}; diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-logger/src/locale/ja-JP.json new file mode 100644 index 0000000000..4dee8136ff --- /dev/null +++ b/packages/plugins/@nocobase/plugin-logger/src/locale/ja-JP.json @@ -0,0 +1,9 @@ +{ + "Logger": "ログ", + "Search": "検索", + "Download": "ダウンロード", + "Download logs": "ログをダウンロード", + "API request and response logs": "API リクエストとレスポンスのログ", + "Application, database, plugins and other system logs, the error level logs will be sent to": "アプリケーション、データベース、プラグイン、およびその他のシステムログ、エラーレベルのログは次に送信されます", + "SQL execution logs, printed by Sequelize when the db logging is enabled": "SQL 実行ログ、データベースログが有効な場合、Sequelize が出力する SQL 実行ログ" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/ja_JP.ts b/packages/plugins/@nocobase/plugin-logger/src/locale/ja_JP.ts deleted file mode 100644 index 1d912ef964..0000000000 --- a/packages/plugins/@nocobase/plugin-logger/src/locale/ja_JP.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - "Logger": "ログ", - "Search": "検索", - "Download": "ダウンロード", - "Download logs": "ログをダウンロード", - "API request and response logs": "API リクエストとレスポンスのログ", - "Application, database, plugins and other system logs, the error level logs will be sent to": "アプリケーション、データベース、プラグイン、およびその他のシステムログ、エラーレベルのログは次に送信されます", - "SQL execution logs, printed by Sequelize when the db logging is enabled": "SQL 実行ログ、データベースログが有効な場合、Sequelize が出力する SQL 実行ログ", -}; \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/ko-KR.json b/packages/plugins/@nocobase/plugin-logger/src/locale/ko-KR.json new file mode 100644 index 0000000000..5f5d6f7613 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-logger/src/locale/ko-KR.json @@ -0,0 +1,9 @@ +{ + "Logger": "기록기", + "Search": "검색", + "Download": "다운로드", + "Download logs": "로그 다운로드", + "API request and response logs": "API 요청 및 응답 로그", + "Application, database, plugins and other system logs, the error level logs will be sent to": "응용 프로그램, 데이터베이스, 플러그인 및 기타 시스템 로그, 오류 수준 로그가 전송됩니다:", + "SQL execution logs, printed by Sequelize when the db logging is enabled": "데이터베이스 로깅이 활성화되어 있을 때 Sequelize에서 인쇄하는 SQL 실행 로그" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/ko_KR.ts b/packages/plugins/@nocobase/plugin-logger/src/locale/ko_KR.ts deleted file mode 100644 index c9b4cbe6a2..0000000000 --- a/packages/plugins/@nocobase/plugin-logger/src/locale/ko_KR.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - "Logger": "기록기", - "Search": "검색", - "Download": "다운로드", - "Download logs": "로그 다운로드", - "API request and response logs": "API 요청 및 응답 로그", - "Application, database, plugins and other system logs, the error level logs will be sent to": - "응용 프로그램, 데이터베이스, 플러그인 및 기타 시스템 로그, 오류 수준 로그가 전송됩니다:", - "SQL execution logs, printed by Sequelize when the db logging is enabled": - "데이터베이스 로깅이 활성화되어 있을 때 Sequelize에서 인쇄하는 SQL 실행 로그" -}; diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-logger/src/locale/zh-CN.json new file mode 100644 index 0000000000..3631b59804 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-logger/src/locale/zh-CN.json @@ -0,0 +1,9 @@ +{ + "Logger": "日志", + "Search": "搜索", + "Download": "下载", + "Download logs": "下载日志", + "API request and response logs": "API 接口请求和响应日志", + "Application, database, plugins and other system logs, the error level logs will be sent to": "应用、数据库、插件和其他系统日志,错误级别日志将会打印到", + "SQL execution logs, printed by Sequelize when the db logging is enabled": "SQL 执行日志, 数据库日志启用时,Sequelize 打印的 SQL 执行日志" +} \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-logger/src/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-logger/src/locale/zh-CN.ts deleted file mode 100644 index 9898718bff..0000000000 --- a/packages/plugins/@nocobase/plugin-logger/src/locale/zh-CN.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -export default { - Logger: '日志', - Search: '搜索', - Download: '下载', - 'Download logs': '下载日志', - 'API request and response logs': 'API 接口请求和响应日志', - 'Application, database, plugins and other system logs, the error level logs will be sent to': - '应用、数据库、插件和其他系统日志,错误级别日志将会打印到', - 'SQL execution logs, printed by Sequelize when the db logging is enabled': - 'SQL 执行日志, 数据库日志启用时,Sequelize 打印的 SQL 执行日志', -}; diff --git a/packages/plugins/@nocobase/plugin-map/README.md b/packages/plugins/@nocobase/plugin-map/README.md index d5fe6974a5..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-map/README.md +++ b/packages/plugins/@nocobase/plugin-map/README.md @@ -1,13 +1,30 @@ -# map +# NocoBase -English | [中文](./README.zh-CN.md) + -地图插件。 -## 安装激活 +## What is NocoBase -```bash -yarn pm enable map -``` +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 使用方法 +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-map/README.zh-CN.md b/packages/plugins/@nocobase/plugin-map/README.zh-CN.md deleted file mode 100644 index a8b6938a54..0000000000 --- a/packages/plugins/@nocobase/plugin-map/README.zh-CN.md +++ /dev/null @@ -1,13 +0,0 @@ -# map - -[English](./README.md) | 中文 - -地图插件。 - -## 安装激活 - -```bash -yarn pm enable map -``` - -## 使用方法 diff --git a/packages/plugins/@nocobase/plugin-map/package.json b/packages/plugins/@nocobase/plugin-map/package.json index 77b4249cd2..e4f4aa6d7f 100644 --- a/packages/plugins/@nocobase/plugin-map/package.json +++ b/packages/plugins/@nocobase/plugin-map/package.json @@ -2,7 +2,7 @@ "name": "@nocobase/plugin-map", "displayName": "Block: Map", "displayName.zh-CN": "区块:地图", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "description": "Map block, support Gaode map and Google map, you can also extend more map types.", "description.zh-CN": "地图区块,支持高德地图和 Google 地图,你也可以扩展更多地图类型。", "license": "AGPL-3.0", diff --git a/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaInitializer.test.ts b/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaInitializer.test.ts index 3e3c7bb1ec..d408b10e9d 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaInitializer.test.ts +++ b/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaInitializer.test.ts @@ -29,7 +29,7 @@ test.describe('where map block can be added', () => { // 2. 点击跳转按钮去配置页面,配置好后返回刚才的页面,应该能正常显示地图 await page.getByRole('button', { name: 'Go to the configuration page' }).click(); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); await page.waitForTimeout(1000); if (await page.getByRole('button', { name: 'Edit' }).first().isVisible()) { await page.getByRole('button', { name: 'Edit' }).first().click(); @@ -49,7 +49,7 @@ test.describe('where map block can be added', () => { // 4. 清空配置信息,以免影响其他测试用例 await page.goto('/admin/settings/map'); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); await page.waitForTimeout(1000); await page.getByRole('button', { name: 'Edit' }).first().click(); await page.getByLabel('Access key').clear(); diff --git a/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaSettings.test.ts b/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaSettings.test.ts index d70113dd32..82bf101fd2 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaSettings.test.ts +++ b/packages/plugins/@nocobase/plugin-map/src/client/__e2e__/schemaSettings.test.ts @@ -12,7 +12,7 @@ import { oneMapUsedToTestSettings } from './templates'; test.beforeEach(async ({ page }) => { await page.goto('/admin/settings/map'); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); await page.waitForTimeout(1000); if (await page.getByRole('button', { name: 'Edit' }).first().isVisible()) { await page.getByRole('button', { name: 'Edit' }).first().click(); @@ -26,7 +26,7 @@ test.beforeEach(async ({ page }) => { test.afterEach(async ({ page }) => { await page.goto('/admin/settings/map'); - await page.waitForLoadState('networkidle'); + await page.waitForLoadState('load'); await page.waitForTimeout(1000); await page.getByRole('button', { name: 'Edit' }).first().click(); await page.getByLabel('Access key').clear(); diff --git a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.Settings.tsx b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.Settings.tsx index f373cfc0c3..e6cce51f3a 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.Settings.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/block/MapBlock.Settings.tsx @@ -26,6 +26,7 @@ import { useCollectionManager_deprecated, useDesignable, useFormBlockContext, + useColumnSchema, } from '@nocobase/client'; import _ from 'lodash'; import { useMapTranslation } from '../locale'; @@ -71,6 +72,10 @@ export const defaultZoomLevel = { }, }; }, + useVisible() { + const { fieldSchema: tableColumnSchema } = useColumnSchema(); + return !tableColumnSchema; + }, }; export const mapBlockSettings = new SchemaSettings({ diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Map.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Map.tsx index 4239bcba12..c1fd3798e4 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Map.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/components/AMap/Map.tsx @@ -335,6 +335,24 @@ export const AMapComponent = React.forwardRef { + map.current = new AMap.Map(id.current, { + resizeEnable: true, + zoom, + } as AMap.MapOptions); + aMap.current = AMap; + setErrMessage(''); + forceUpdate([]); + }); + return; + } catch (err) { + setErrMessage(err); + } + } + AMapLoader.load({ key: accessKey, version: '2.0', diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/MapComponent.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/MapComponent.tsx index a5bf4a71d8..b436bfc9bb 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/components/MapComponent.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/components/MapComponent.tsx @@ -27,7 +27,6 @@ export const MapComponent = React.forwardRef((props, ref) => { if (!Component) { return
{t(`The ${mapType} cannot found`)}
; } - return ; }); MapComponent.displayName = 'MapComponent'; diff --git a/packages/plugins/@nocobase/plugin-map/src/client/components/ReadPretty.tsx b/packages/plugins/@nocobase/plugin-map/src/client/components/ReadPretty.tsx index f331418a2e..0fa540448f 100644 --- a/packages/plugins/@nocobase/plugin-map/src/client/components/ReadPretty.tsx +++ b/packages/plugins/@nocobase/plugin-map/src/client/components/ReadPretty.tsx @@ -7,21 +7,21 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useFieldSchema, useForm } from '@formily/react'; -import { EllipsisWithTooltip, useCollection_deprecated, useFieldTitle } from '@nocobase/client'; +import { useField, useFieldSchema, useForm } from '@formily/react'; +import { EllipsisWithTooltip, useCollection, useFieldTitle } from '@nocobase/client'; import React from 'react'; import { MapComponent } from './MapComponent'; const ReadPretty = (props) => { const { value } = props; const fieldSchema = useFieldSchema(); - const { getField } = useCollection_deprecated(); - const collectionField = getField(fieldSchema.name); + const collection = useCollection(); + const collectionField = collection.getField(fieldSchema.name); const mapType = props.mapType || collectionField?.uiSchema['x-component-props']?.mapType; const form = useForm(); + const field = useField(); useFieldTitle(); - - if (!form.readPretty) { + if (!form.readPretty || field.readPretty) { return (
diff --git a/packages/plugins/@nocobase/plugin-map/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-map/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-map/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-map/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-map/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-map/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-map/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-map/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-mobile-client/package.json b/packages/plugins/@nocobase/plugin-mobile-client/package.json index 4a3dedbd6a..21ffb0ed8a 100644 --- a/packages/plugins/@nocobase/plugin-mobile-client/package.json +++ b/packages/plugins/@nocobase/plugin-mobile-client/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-mobile-client", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/mobile-client", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/mobile-client", @@ -18,7 +18,7 @@ "@types/react-dom": "17.x", "ahooks": "3.x", "antd": "5.x", - "antd-mobile": "^5.29.1", + "antd-mobile": "^5.38", "antd-style": "3.x", "classnames": "2.x", "react": "18.x", diff --git a/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx b/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx index 250d2a3c76..53dceb137a 100644 --- a/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx +++ b/packages/plugins/@nocobase/plugin-mobile-client/src/client/core/schema/components/tab-bar/TabBar.Item.tsx @@ -37,6 +37,7 @@ const InternalItem: React.FC = () => { height: 100%; top: 0; left: 0; + padding-top: 5px; `, )} > diff --git a/packages/plugins/@nocobase/plugin-mobile-client/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-mobile-client/src/locale/ja-JP.json similarity index 100% rename from packages/plugins/@nocobase/plugin-mobile-client/src/locale/ja_JP.json rename to packages/plugins/@nocobase/plugin-mobile-client/src/locale/ja-JP.json diff --git a/packages/plugins/@nocobase/plugin-mobile-client/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-mobile-client/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-mobile-client/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-mobile-client/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-mobile/README.md b/packages/plugins/@nocobase/plugin-mobile/README.md index c7a2d64053..d3e8120f6f 100644 --- a/packages/plugins/@nocobase/plugin-mobile/README.md +++ b/packages/plugins/@nocobase/plugin-mobile/README.md @@ -1,15 +1,30 @@ -# Mobile +# NocoBase -English | [中文](./README.zh-CN.md) + -多应用管理插件。 -## 安装激活 +## What is NocoBase -```bash -yarn pm enable @nocobase/plugin-mobile -``` +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! -## 文档 +Homepage: +https://www.nocobase.com/ -[使用文档](https://docs.nocobase.com/handbook/mobile) +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/plugins/@nocobase/plugin-mobile/README.zh-CN.md b/packages/plugins/@nocobase/plugin-mobile/README.zh-CN.md deleted file mode 100644 index b766ee8159..0000000000 --- a/packages/plugins/@nocobase/plugin-mobile/README.zh-CN.md +++ /dev/null @@ -1,15 +0,0 @@ -# Mobile - -[English](./README.md) | 中文 - -多应用管理插件。 - -## 安装激活 - -```bash -yarn pm enable @nocobase/plugin-mobile -``` - -## 文档 - -[使用文档](https://docs.nocobase.com/handbook/mobile) diff --git a/packages/plugins/@nocobase/plugin-mobile/package.json b/packages/plugins/@nocobase/plugin-mobile/package.json index 33600c2a9a..eab59b08d7 100644 --- a/packages/plugins/@nocobase/plugin-mobile/package.json +++ b/packages/plugins/@nocobase/plugin-mobile/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-mobile", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/mobile", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/mobile", @@ -11,25 +11,25 @@ "description.zh-CN": "提供移动端页面配置的能力。", "peerDependencies": { "@nocobase/client": "1.x", + "@nocobase/plugin-acl": "1.x", "@nocobase/server": "1.x", - "@nocobase/test": "1.x", - "@nocobase/plugin-acl": "1.x" + "@nocobase/test": "1.x" }, "devDependencies": { "@ant-design/icons": "5.x", + "@emotion/css": "11.x", "@formily/antd-v5": "1.x", + "@formily/core": "2.x", "@formily/react": "2.x", "@formily/shared": "2.x", "@types/react": "17.x", "@types/react-dom": "17.x", - "antd-mobile": "^5.36.1", + "ahooks": "3.x", + "antd": "5.x", + "antd-mobile": "^5.38", + "lodash": "4.x", "re-resizable": "6.6.0", "react-device-detect": "2.2.3", - "@emotion/css": "11.x", - "ahooks": "3.x", - "lodash": "4.x", - "@formily/core": "2.x", - "antd": "5.x", "react-i18next": "11.x" } } diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/templates.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/templates.ts new file mode 100644 index 0000000000..33a52b9c34 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/templates.ts @@ -0,0 +1,1065 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export const shouldDisplayImageNormally = { + collections: [ + { + name: 'image', + fields: [ + { + name: 'attachment', + interface: 'attachment', + }, + ], + }, + ], + pageSchema: { + type: 'void', + 'x-component': 'Grid', + 'x-component-props': { + showDivider: false, + }, + 'x-initializer': 'mobile:addBlock', + properties: { + '2qkpsi1o454': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.42-beta', + properties: { + n7plq4w7u4j: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.42-beta', + properties: { + vi2vd17p09w: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'image:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'image', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.42-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.42-beta', + 'x-uid': 'xdkvvipejgd', + 'x-async': false, + 'x-index': 1, + }, + '61g0b1ddb4a': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.42-beta', + properties: { + actions: { + 'x-uid': 'rjpymlq1jzw', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.42-beta', + 'x-component-props': { + width: 50, + }, + properties: { + nj2c3kinca5: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.42-beta', + properties: { + dzdmqaizn2s: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("View") }}', + 'x-action': 'view', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:view', + 'x-component': 'Action.Link', + 'x-component-props': { + openMode: 'page', + }, + 'x-action-context': { + dataSource: 'main', + collection: 'image', + }, + 'x-decorator': 'ACLActionProvider', + 'x-designer-props': { + linkageAction: true, + }, + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("View record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Details")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'popup:common:addBlock', + properties: { + v3lu0q5qb2s: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.42-beta', + properties: { + '8v6rb76xzcl': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.42-beta', + properties: { + j5mq2jtfqbu: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action': 'image:get', + 'x-decorator': 'DetailsBlockProvider', + 'x-use-decorator-props': 'useDetailsDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'image', + readPretty: true, + action: 'get', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:details', + 'x-component': 'CardItem', + 'x-app-version': '1.3.42-beta', + properties: { + l2d5m4sojzb: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Details', + 'x-read-pretty': true, + 'x-use-component-props': 'useDetailsProps', + 'x-app-version': '1.3.42-beta', + properties: { + ilctz1mj532: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'details:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 24, + }, + }, + 'x-app-version': '1.3.42-beta', + properties: { + lhm4dx0xm3r: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Edit") }}', + 'x-action': 'update', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:edit', + 'x-component': 'Action', + 'x-component-props': { + openMode: 'page', + icon: 'EditOutlined', + type: 'primary', + }, + 'x-action-context': { + dataSource: 'main', + collection: 'image', + }, + 'x-decorator': 'ACLActionProvider', + 'x-app-version': '1.3.42-beta', + properties: { + drawer: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Edit record") }}', + 'x-component': 'Action.Container', + 'x-component-props': { + className: 'nb-action-popup', + }, + 'x-app-version': '1.3.42-beta', + properties: { + tabs: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Tabs', + 'x-component-props': {}, + 'x-initializer': 'popup:addTab', + 'x-app-version': '1.3.42-beta', + properties: { + tab1: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{t("Edit")}}', + 'x-component': 'Tabs.TabPane', + 'x-designer': 'Tabs.Designer', + 'x-component-props': {}, + 'x-app-version': '1.3.42-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': + 'popup:common:addBlock', + 'x-app-version': '1.3.42-beta', + properties: { + '5pwp67hvgii': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': + '1.3.42-beta', + properties: { + b02yo5jzqu1: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.42-beta', + properties: { + '6bsqjxlo2l0': { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-acl-action': + 'image:get', + 'x-decorator': + 'DetailsBlockProvider', + 'x-use-decorator-props': + 'useDetailsDecoratorProps', + 'x-decorator-props': + { + dataSource: + 'main', + collection: + 'image', + readPretty: + true, + action: 'get', + }, + 'x-toolbar': + 'BlockSchemaToolbar', + 'x-settings': + 'blockSettings:details', + 'x-component': + 'CardItem', + 'x-app-version': + '1.3.42-beta', + properties: { + juho6f9cld0: { + _isJSONSchemaObject: + true, + version: '2.0', + type: 'void', + 'x-component': + 'Details', + 'x-read-pretty': + true, + 'x-use-component-props': + 'useDetailsProps', + 'x-app-version': + '1.3.42-beta', + properties: { + '6xrgjg0w2j0': + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-initializer': + 'details:configureActions', + 'x-component': + 'ActionBar', + 'x-component-props': + { + style: + { + marginBottom: 24, + }, + }, + 'x-app-version': + '1.3.42-beta', + properties: + { + tqtunfb7c5a: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + title: + '{{ t("Edit") }}', + 'x-action': + 'update', + 'x-toolbar': + 'ActionSchemaToolbar', + 'x-settings': + 'actionSettings:edit', + 'x-component': + 'Action', + 'x-component-props': + { + openMode: + 'page', + icon: 'EditOutlined', + type: 'primary', + }, + 'x-action-context': + { + dataSource: + 'main', + collection: + 'image', + }, + 'x-decorator': + 'ACLActionProvider', + 'x-app-version': + '1.3.42-beta', + properties: + { + drawer: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + title: + '{{ t("Edit record") }}', + 'x-component': + 'Action.Container', + 'x-component-props': + { + className: + 'nb-action-popup', + }, + 'x-app-version': + '1.3.42-beta', + properties: + { + tabs: { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Tabs', + 'x-component-props': + {}, + 'x-initializer': + 'popup:addTab', + 'x-app-version': + '1.3.42-beta', + properties: + { + tab1: { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + title: + '{{t("Edit")}}', + 'x-component': + 'Tabs.TabPane', + 'x-designer': + 'Tabs.Designer', + 'x-component-props': + {}, + 'x-app-version': + '1.3.42-beta', + properties: + { + grid: { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid', + 'x-initializer': + 'popup:common:addBlock', + 'x-app-version': + '1.3.42-beta', + properties: + { + xswkkgymq30: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid.Row', + 'x-app-version': + '1.3.42-beta', + properties: + { + pq2gdv03h0f: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.42-beta', + properties: + { + pmoy87u1q83: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-acl-action': + 'image:get', + 'x-decorator': + 'DetailsBlockProvider', + 'x-use-decorator-props': + 'useDetailsDecoratorProps', + 'x-decorator-props': + { + dataSource: + 'main', + collection: + 'image', + readPretty: + true, + action: + 'get', + }, + 'x-toolbar': + 'BlockSchemaToolbar', + 'x-settings': + 'blockSettings:details', + 'x-component': + 'CardItem', + 'x-app-version': + '1.3.42-beta', + properties: + { + krrpne6bx2e: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Details', + 'x-read-pretty': + true, + 'x-use-component-props': + 'useDetailsProps', + 'x-app-version': + '1.3.42-beta', + properties: + { + '4eixb6olg5s': + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-initializer': + 'details:configureActions', + 'x-component': + 'ActionBar', + 'x-component-props': + { + style: + { + marginBottom: 24, + }, + }, + 'x-app-version': + '1.3.42-beta', + 'x-uid': + 'mtk4qib0pmb', + 'x-async': + false, + 'x-index': 1, + }, + grid: { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid', + 'x-initializer': + 'details:configureFields', + 'x-app-version': + '1.3.42-beta', + properties: + { + '5qv0bbqybgm': + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid.Row', + 'x-app-version': + '1.3.42-beta', + properties: + { + x1e4gnfeupv: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.42-beta', + properties: + { + attachment: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'string', + 'x-toolbar': + 'FormItemSchemaToolbar', + 'x-settings': + 'fieldSettings:FormItem', + 'x-component': + 'CollectionField', + 'x-decorator': + 'FormItem', + 'x-collection-field': + 'image.attachment', + 'x-component-props': + {}, + 'x-use-component-props': + 'useAttachmentFieldProps', + 'x-app-version': + '1.3.42-beta', + 'x-uid': + 'dhcu2vf5fxm', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'yl57to4ttjy', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'eyqz6iaciv5', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '0sx8rthcy15', + 'x-async': + false, + 'x-index': 2, + }, + }, + 'x-uid': + 'uh0vhvhnltl', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'ogkh4von4yu', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'lhgnxv5urvf', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '3rdklcguzy1', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'kbzczrd39z3', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'saexcyuo5ao', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'l6gxsphiv7p', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '5j05xhjnvhu', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '9qid8vlu438', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'hnt0v2iaf1q', + 'x-async': + false, + 'x-index': 1, + }, + grid: { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid', + 'x-initializer': + 'details:configureFields', + 'x-app-version': + '1.3.42-beta', + properties: + { + '4ljipl6fcex': + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid.Row', + 'x-app-version': + '1.3.42-beta', + properties: + { + o28cy7so1co: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'void', + 'x-component': + 'Grid.Col', + 'x-app-version': + '1.3.42-beta', + properties: + { + attachment: + { + _isJSONSchemaObject: + true, + version: + '2.0', + type: 'string', + 'x-toolbar': + 'FormItemSchemaToolbar', + 'x-settings': + 'fieldSettings:FormItem', + 'x-component': + 'CollectionField', + 'x-decorator': + 'FormItem', + 'x-collection-field': + 'image.attachment', + 'x-component-props': + {}, + 'x-use-component-props': + 'useAttachmentFieldProps', + 'x-app-version': + '1.3.42-beta', + 'x-uid': + 'u19h5djk1af', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 't1m1yr1sddj', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'h3ok06z8mnt', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + 'gfw19ntb1ig', + 'x-async': + false, + 'x-index': 2, + }, + }, + 'x-uid': + '5lreqos5pcm', + 'x-async': + false, + 'x-index': 1, + }, + }, + 'x-uid': + '28dc9dbzmnd', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '0nwx7uvpdrw', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'vh98qnnifff', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'nklyrutj2hw', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'jzi0d8nhv9t', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'wiru51cp3bj', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'kcy9s6f3kll', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'k52k7dy2jyh', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'e9xf3xhivpu', + 'x-async': false, + 'x-index': 1, + }, + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'details:configureFields', + 'x-app-version': '1.3.42-beta', + properties: { + pmh5rz8nxf0: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.42-beta', + properties: { + wdi21i7x7th: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.42-beta', + properties: { + attachment: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': + 'image.attachment', + 'x-component-props': {}, + 'x-use-component-props': + 'useAttachmentFieldProps', + 'x-app-version': '1.3.42-beta', + 'x-uid': 'nsxtdx4v0on', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '7jwrrijskjo', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ioxlkouqs1s', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'c7v6g526r3o', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'xvnohfnt59r', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '2w9fddhi7ig', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '2wm58u6r3pu', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '1gl08rddg53', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ljevsl3tauf', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ldqvb7su74d', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'a6vwhe6f4di', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ufel6b95xab', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ynfe2e3advc', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'muv6sn88u21', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + aeie2yj3330: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.3.42-beta', + properties: { + attachment: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'image.attachment', + 'x-component': 'CollectionField', + 'x-component-props': { + size: 'small', + }, + 'x-read-pretty': true, + 'x-decorator': null, + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-use-component-props': 'useAttachmentFieldProps', + 'x-app-version': '1.3.42-beta', + 'x-uid': 'v8ob310s6rw', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'nl49qq20w9u', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'mmmy06sb66h', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': '544lfyanyxa', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '8o70uof3o2h', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'hi06e74rl8e', + 'x-async': false, + 'x-index': 1, + }, + }, + name: 'q8vu8vaasre', + 'x-uid': 'q8vu8vaasre', + 'x-async': true, + 'x-index': 1, + }, +}; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/zIndex.test.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/zIndex.test.ts new file mode 100644 index 0000000000..625bdc8cc9 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/__e2e__/zIndex.test.ts @@ -0,0 +1,72 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { expect, test } from '@nocobase/test/e2e'; +import { shouldDisplayImageNormally } from './templates'; + +test.describe('zIndex', () => { + test('should display image normally', async ({ page, mockMobilePage, mockRecord }) => { + const nocoPage = await mockMobilePage(shouldDisplayImageNormally).waitForInit(); + const record = await mockRecord('image'); + await nocoPage.goto(); + + const title = record.attachment[0].title; + + // 通过鼠标 hover 到 Add block 按钮来检查是否符合预期 + const check = async (level: number) => { + try { + switch (level) { + case 0: + await page.getByLabel('schema-initializer-Grid-').hover({ timeout: 300 }); + break; + case 1: + await page.getByLabel('schema-initializer-Grid-popup').hover({ timeout: 300 }); + break; + case 2: + await page.getByLabel('schema-initializer-Grid-popup').nth(1).hover({ timeout: 300 }); + break; + case 3: + await page.getByLabel('schema-initializer-Grid-popup').nth(2).hover({ timeout: 300 }); + break; + } + } catch (e) { + return; + } + await page.waitForTimeout(300); + await expect(page.getByText('Desktop data blocks')).not.toBeVisible(); + }; + + // 1. 在主页面,点击图片预览,图片不能被主页面盖住 + await page.getByRole('link', { name: title }).click(); + await page.waitForTimeout(300); + await check(0); + await page.getByLabel('Close lightbox').click(); + + // 2. 进入第一层子页面,然后点击图片预览, 图片不能被子页面盖住 + await page.getByLabel('action-Action.Link-View-view-').click(); + await page.getByRole('link', { name: title }).nth(1).click(); + await page.waitForTimeout(300); + await check(1); + await page.getByLabel('Close lightbox').click(); + + // 3. 进入第二层子页面,然后点击图片预览, 图片不能被子页面盖住 + await page.getByLabel('action-Action-Edit-update-').click(); + await page.getByRole('link', { name: title }).nth(2).click(); + await page.waitForTimeout(300); + await check(2); + await page.getByLabel('Close lightbox').click(); + + // 4. 进入第三层子页面,然后点击图片预览, 图片不能被子页面盖住 + await page.getByLabel('action-Action-Edit-update-').nth(2).click(); + await page.getByRole('link', { name: title }).nth(3).click(); + await page.waitForTimeout(300); + await check(3); + await page.getByLabel('Close lightbox').click(); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx index 6e0e7baea6..39a2ebd34d 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/ActionDrawer.tsx @@ -8,14 +8,14 @@ */ import { ISchema, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; -import { Action, SchemaComponent, useActionContext } from '@nocobase/client'; +import { Action, SchemaComponent, useActionContext, useZIndexContext, zIndexContext } from '@nocobase/client'; import { ConfigProvider } from 'antd'; import { Popup } from 'antd-mobile'; import { CloseOutline } from 'antd-mobile-icons'; import React, { useCallback, useEffect, useMemo } from 'react'; import { useMobileActionDrawerStyle } from './ActionDrawer.style'; -import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider'; import { usePopupContainer } from './FilterAction'; +import { MIN_Z_INDEX_INCREMENT } from './zIndex'; export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: string }) => { const fieldSchema = useFieldSchema(); @@ -23,7 +23,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: const { visible, setVisible } = useActionContext(); const { popupContainerRef, visiblePopup } = usePopupContainer(visible); const { styles } = useMobileActionDrawerStyle(); - const { basicZIndex } = useBasicZIndex(); + const parentZIndex = useZIndexContext(); // this schema need to add padding in the content area of the popup const isSpecialSchema = isChangePasswordSchema(fieldSchema) || isEditProfileSchema(fieldSchema); @@ -32,7 +32,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: const specialStyle = isSpecialSchema ? { backgroundColor: 'white' } : {}; - const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; + const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT; const zIndexStyle = useMemo(() => { return { @@ -66,7 +66,7 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?: }, [newZIndex]); return ( - + - + ); }); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/BasicZIndexProvider.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/BasicZIndexProvider.tsx deleted file mode 100644 index c802e0d3d1..0000000000 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/BasicZIndexProvider.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import React, { useMemo } from 'react'; - -const BasicZIndexContext = React.createContext<{ - basicZIndex: number; -}>({ - basicZIndex: 0, -}); - -/** - * used to accumulate z-index in nested popups - * @param props - * @returns - */ -export const BasicZIndexProvider: React.FC<{ basicZIndex: number }> = (props) => { - const value = useMemo(() => ({ basicZIndex: props.basicZIndex }), [props.basicZIndex]); - return {props.children}; -}; - -export const useBasicZIndex = () => { - return React.useContext(BasicZIndexContext); -}; - -// minimum z-index increment -export const MIN_Z_INDEX_INCREMENT = 10; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/FilterAction.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/FilterAction.tsx index 4370e8449b..0a21e37e94 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/FilterAction.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/FilterAction.tsx @@ -7,14 +7,14 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Filter, withDynamicSchemaProps } from '@nocobase/client'; +import { Filter, useZIndexContext, withDynamicSchemaProps } from '@nocobase/client'; import { ConfigProvider } from 'antd'; import { Popup } from 'antd-mobile'; import { CloseOutline } from 'antd-mobile-icons'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useMobileActionDrawerStyle } from './ActionDrawer.style'; -import { MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider'; +import { MIN_Z_INDEX_INCREMENT } from './zIndex'; const OriginFilterAction = Filter.Action; @@ -24,11 +24,11 @@ export const FilterAction = withDynamicSchemaProps((props) => { {...props} Container={(props) => { const { visiblePopup, popupContainerRef } = usePopupContainer(props.open); - const { basicZIndex } = useBasicZIndex(); + const parentZIndex = useZIndexContext(); const { styles } = useMobileActionDrawerStyle(); const { t } = useTranslation(); - const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; + const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT; // eslint-disable-next-line react-hooks/rules-of-hooks const closePopup = useCallback(() => { @@ -116,9 +116,9 @@ export const usePopupContainer = (visible: boolean) => { const [mobileContainer] = useState(() => document.querySelector('.mobile-container')); const [visiblePopup, setVisiblePopup] = useState(false); const popupContainerRef = React.useRef(null); - const { basicZIndex } = useBasicZIndex(); + const parentZIndex = useZIndexContext(); - const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; + const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT; useEffect(() => { if (!visible) { diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/InternalPopoverNester.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/InternalPopoverNester.tsx index 3ce77d41d9..03207a8a70 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/InternalPopoverNester.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/InternalPopoverNester.tsx @@ -8,22 +8,23 @@ */ import { useField } from '@formily/react'; +import { useZIndexContext, zIndexContext } from '@nocobase/client'; import { ConfigProvider } from 'antd'; import { Popup } from 'antd-mobile'; import { CloseOutline } from 'antd-mobile-icons'; import React, { useCallback, useMemo } from 'react'; -import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from './BasicZIndexProvider'; import { usePopupContainer } from './FilterAction'; import { useInternalPopoverNesterUsedInMobileStyle } from './InternalPopoverNester.style'; +import { MIN_Z_INDEX_INCREMENT } from './zIndex'; const Container = (props) => { const { onOpenChange } = props; const { visiblePopup, popupContainerRef } = usePopupContainer(props.open); const { styles } = useInternalPopoverNesterUsedInMobileStyle(); const field = useField(); - const { basicZIndex } = useBasicZIndex(); + const parentZIndex = useZIndexContext(); - const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT; + const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT; const title = field.title || ''; const zIndexStyle = useMemo(() => { @@ -49,7 +50,7 @@ const Container = (props) => { }, [newZIndex]); return ( - +
{props.children}
{
-
+ ); }; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/mobile-action-page/MobileActionPage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/mobile-action-page/MobileActionPage.tsx index 891871d14f..066cbebe18 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/mobile-action-page/MobileActionPage.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/mobile-action-page/MobileActionPage.tsx @@ -14,10 +14,12 @@ import { TabsContextProvider, useActionContext, useTabsContext, + useZIndexContext, + zIndexContext, } from '@nocobase/client'; import React, { useMemo } from 'react'; import { createPortal } from 'react-dom'; -import { BasicZIndexProvider, MIN_Z_INDEX_INCREMENT, useBasicZIndex } from '../BasicZIndexProvider'; +import { MIN_Z_INDEX_INCREMENT } from '../zIndex'; import { useMobileActionPageStyle } from './MobileActionPage.style'; import { MobileTabsForMobileActionPage } from './MobileTabsForMobileActionPage'; @@ -34,11 +36,11 @@ export const MobileActionPage = ({ level, footerNodeName }) => { const { styles } = useMobileActionPageStyle(); const tabContext = useTabsContext(); const containerDOM = useMemo(() => document.querySelector('.nb-mobile-subpages-slot'), []); - const { basicZIndex } = useBasicZIndex(); + const parentZIndex = useZIndexContext(); // in nested popups, basicZIndex is an accumulated value to ensure that // the z-index of the current level is always higher than the previous level - const newZIndex = basicZIndex + MIN_Z_INDEX_INCREMENT + (level || 1); + const newZIndex = parentZIndex + MIN_Z_INDEX_INCREMENT + (level || 1); const footerSchema = fieldSchema.reduceProperties((buf, s) => { if (s['x-component'] === footerNodeName) { @@ -58,7 +60,7 @@ export const MobileActionPage = ({ level, footerNodeName }) => { } const actionPageNode = ( - +
} tabBarGutter={48}> @@ -76,7 +78,7 @@ export const MobileActionPage = ({ level, footerNodeName }) => {
)}
- + ); if (containerDOM) { diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/zh-CN.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/zIndex.ts similarity index 80% rename from packages/plugins/@nocobase/plugin-api-keys/src/server/locale/zh-CN.ts rename to packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/zIndex.ts index c81f3e750f..f59af7ae7e 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/src/server/locale/zh-CN.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/adaptor-of-desktop/zIndex.ts @@ -7,6 +7,5 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -export default { - 'Role not found': '角色不存在', -}; +// minimum z-index increment +export const MIN_Z_INDEX_INCREMENT = 10; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx index ff95766c5a..b0326d440c 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/MobileTabBar.Item.tsx @@ -47,13 +47,14 @@ export const MobileTabBarItem: FC = (props) => { })} style={{ lineHeight: 1 }} > - + {icon} {title} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts index 22c9d4222e..4640b57876 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.Item/schema.ts @@ -1,8 +1,19 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + import { upperFirst } from 'lodash'; +import { merge } from '@nocobase/utils/client'; +import { ISchema } from '@nocobase/client'; import { MobileRouteItem } from '../../../mobile-providers'; export function getMobileTabBarItemSchema(routeItem: MobileRouteItem) { - return { + const _schema = { name: routeItem.id, type: 'void', 'x-decorator': 'BlockItem', @@ -19,4 +30,5 @@ export function getMobileTabBarItemSchema(routeItem: MobileRouteItem) { ...(routeItem.options || {}), }, }; + return merge(_schema, routeItem.options?.schema ?? {}) as ISchema; } diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx index 41d7012378..8e7c3915cd 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx @@ -32,7 +32,7 @@ export const MobileTabBar: FC & { Page: typeof MobileTabBarPage; Link: typeof MobileTabBarLink; } = ({ enableTabBar = true }) => { - const { styles } = useStyles(); + const { styles } = useStyles() as any; const { designable } = useDesignable(); const { routeList, activeTabBarItem, resource, refresh } = useMobileRoutes(); const validRouteList = routeList.filter((item) => item.schemaUid || isInnerLink(item.options?.url)); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts index 8ea9531cbb..13a7084e16 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/styles.ts @@ -18,6 +18,7 @@ export const useStyles = createStyles(() => ({ right: 0, height: NavigationBarHeight, boxSizing: 'border-box', + padding: '2px 0px', borderTop: '1px solid var(--adm-color-border)', backgroundColor: 'var(--adm-color-background)', }, diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx index 5bc842a77f..584c1491ee 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/types/MobileTabBar.Page/MobileTabBar.Page.tsx @@ -14,13 +14,18 @@ import { MobileTabBarItemProps, MobileTabBarItem } from '../../MobileTabBar.Item export interface MobileTabBarPageProps extends Omit { schemaUid: string; + url?: string; } export const MobileTabBarPage: FC = (props) => { const { schemaUid, ...rests } = props; const navigate = useNavigate(); const location = useLocation(); - const url = useMemo(() => `/page/${schemaUid}`, [schemaUid]); + const url = useMemo(() => { + if (schemaUid) return `/page/${schemaUid}`; + else if (rests.url) return `${rests.url}`; + else return '/'; + }, [schemaUid, rests.url]); const handleClick = useCallback(() => { navigate(url); }, [url, navigate]); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx index d7c2e11e80..ba01fae7f7 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/Mobile.tsx @@ -18,12 +18,12 @@ import { OpenModeProvider, useAssociationFieldModeContext, usePlugin, + zIndexContext, } from '@nocobase/client'; import React from 'react'; import { isDesktop } from 'react-device-detect'; import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer'; -import { BasicZIndexProvider } from '../adaptor-of-desktop/BasicZIndexProvider'; import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction'; import { InternalPopoverNesterUsedInMobile } from '../adaptor-of-desktop/InternalPopoverNester'; import { useToAddMobilePopupBlockInitializers } from '../adaptor-of-desktop/mobile-action-page/blockInitializers'; @@ -105,9 +105,9 @@ export const Mobile = () => { {/* the z-index of all popups and subpages will be based on this value */} - + - + diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts index 409b42d894..8dd35714bd 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts @@ -18,7 +18,42 @@ export const useStyles = createStyles(({ token, css }) => { & ::-webkit-scrollbar { display: none; } - + .nb-details .ant-formily-item-feedback-layout-loose { + margin-bottom: 5px; + } + .nb-details .ant-formily-item-layout-vertical .ant-formily-item-label { + margin-bottom: -8px; + } + .ant-card .ant-card-body { + padding-bottom: 10px; + padding-top: 10px; + } + .ant-pagination-simple { + margin-top: 0px !important; + } + .nb-action-penal-container { + margin-top: -10px; + margin-bottom: -10px; + } + .nb-action-penal-container + button[aria-label*='schema-initializer-WorkbenchBlock.ActionBar-workbench:configureActions'] { + margin-bottom: 10px; + } + .nb-action-panel { + padding-top: 10px; + } + .nb-action-panel .ant-avatar-circle { + width: 48px !important; + height: 48px !important; + line-height: 48px !important; + } + .nb-chart-block .ant-card .ant-card-body { + padding-bottom: 0px; + padding-top: 0px; + } + .nb-chart-block .noco-card-item { + margin-bottom: -13px; + } .ant-table-thead button[aria-label*='schema-initializer-TableV2-table:configureColumns'] > span:last-child { display: none !important; } @@ -76,6 +111,9 @@ export const useStyles = createStyles(({ token, css }) => { width: 100% !important; max-width: 100% !important; } + .mobile-page-header .adm-tabs-tab { + font-size: 14px; + } `, }; }); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx index 9eaed0c876..9665d10679 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx @@ -7,9 +7,23 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { RemoteSchemaComponent } from '@nocobase/client'; +import { RemoteSchemaComponent, AssociationField } from '@nocobase/client'; import React, { useCallback } from 'react'; import { Outlet, useParams } from 'react-router-dom'; +import { Button as MobileButton, Dialog as MobileDialog } from 'antd-mobile'; +import { MobilePicker } from './components/MobilePicker'; +import { MobileDateTimePicker } from './components/MobileDatePicker'; + +const mobileComponents = { + Button: MobileButton, + Select: MobilePicker, + DatePicker: MobileDateTimePicker, + UnixTimestamp: MobileDateTimePicker, + Modal: MobileDialog, + AssociationField: (props) => { + return ; + }, +}; export const MobilePage = () => { const { pageSchemaUid } = useParams<{ pageSchemaUid: string }>(); @@ -37,6 +51,7 @@ export const MobilePage = () => { NotFoundPage={'MobileNotFoundPage'} memoized={false} onPageNotFind={onPageNotFind} + components={mobileComponents} /> {/* 用于渲染子页面 */} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobileDatePicker.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobileDatePicker.tsx new file mode 100644 index 0000000000..bb00b76f7a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobileDatePicker.tsx @@ -0,0 +1,91 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useState, useCallback } from 'react'; +import { DatePicker } from 'antd-mobile'; +import { mapDatePicker, DatePicker as NBDatePicker } from '@nocobase/client'; +import { connect, mapProps, mapReadPretty } from '@formily/react'; +import { useTranslation } from 'react-i18next'; + +const MobileDateTimePicker = connect( + (props) => { + const { t } = useTranslation(); + const { + value, + onChange, + dateFormat = 'YYYY-MM-DD', + timeFormat = 'HH:mm', + showTime = false, + picker, + ...rest + } = props; + const [visible, setVisible] = useState(false); + + // 性能优化:使用 useCallback 缓存函数 + const handleConfirm = useCallback( + (value) => { + setVisible(false); + const selectedDateTime = new Date(value); + onChange(selectedDateTime); + }, + [showTime, onChange], + ); + + // 清空选择的日期和时间 + const handleClear = useCallback(() => { + setVisible(false); + onChange(null); + }, [onChange]); + + const labelRenderer = useCallback((type: string, data: number) => { + switch (type) { + case 'year': + return data; + case 'quarter': + return data; + default: + return data; + } + }, []); + return ( + <> +
setVisible(true)}> + setVisible(true)} + value={value} + picker={picker} + {...rest} + popupStyle={{ display: 'none' }} + style={{ pointerEvents: 'none', width: '100%' }} + /> +
+ {t('Clear')}} + onClose={() => { + setVisible(false); + }} + precision={showTime ? 'second' : picker === 'date' ? 'day' : picker} + renderLabel={labelRenderer} + min={new Date(1000, 0, 1)} + max={new Date(9999, 11, 31)} + onConfirm={(val) => { + handleConfirm(val); + }} + /> + + ); + }, + mapProps(mapDatePicker()), + mapReadPretty(NBDatePicker.ReadPretty), +); + +export { MobileDateTimePicker }; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobilePicker.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobilePicker.tsx new file mode 100644 index 0000000000..3291e6d6ea --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobilePicker.tsx @@ -0,0 +1,112 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useEffect, useMemo, useState } from 'react'; +import { Button, CheckList, Popup, SearchBar } from 'antd-mobile'; +import { connect, mapProps } from '@formily/react'; +import { Select } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; + +const MobilePicker = connect( + (props) => { + const { value, onChange, options = [], mode } = props; + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const [selected, setSelected] = useState(value || []); + const [searchText, setSearchText] = useState(null); + + const filteredItems = useMemo(() => { + if (searchText) { + return options.filter((item) => item.label.toLowerCase().includes(searchText.toLowerCase())); + } + return options; + }, [options, searchText]); + + const handleConfirm = () => { + onChange(selected); + setVisible(false); + }; + useEffect(() => { + !visible && setSearchText(null); + }, [visible]); + + return ( + <> +
setVisible(true)}> + + {calculatorGroups + .filter((group) => Boolean(getGroupCalculators(group.value).length)) + .map((group) => ( + + {getGroupCalculators(group.value).map(([value, { name }]) => ( + + {compile(name)} + + ))} + + ))} + + + + ); +} + +function CalculationItem({ value, onChange, onRemove }) { + if (!value) { + return null; + } + + const { calculator, operands = [] } = value; + + return ( +
+ {value.group ? ( + onChange({ ...value, group })} /> + ) : ( + + )} +
+ ); +} + +function CalculationGroup({ value, onChange }) { + const { t } = useTranslation(); + const { type = 'and', calculations = [] } = value; + + const onAddSingle = useCallback(() => { + onChange({ + ...value, + calculations: [...calculations, { not: false, calculator: 'equal' }], + }); + }, [value, calculations, onChange]); + + const onAddGroup = useCallback(() => { + onChange({ + ...value, + calculations: [...calculations, { not: false, group: { type: 'and', calculations: [] } }], + }); + }, [value, calculations, onChange]); + + const onRemove = useCallback( + (i: number) => { + calculations.splice(i, 1); + onChange({ ...value, calculations: [...calculations] }); + }, + [value, calculations, onChange], + ); + + const onItemChange = useCallback( + (i: number, v) => { + calculations.splice(i, 1, v); + + onChange({ ...value, calculations: [...calculations] }); + }, + [value, calculations, onChange], + ); + + return ( +
+
+ + {'Meet '} + + {' conditions in the group'} + +
+
+ {calculations.map((calculation, i) => ( + onRemove(i)} + /> + ))} +
+
+ + +
+
+ ); +} + +const VariableHookContext = createContext(useWorkflowVariableOptions); + +export function CalculationConfig({ value, onChange, useVariableHook = useWorkflowVariableOptions }) { + const rule = value && Object.keys(value).length ? value : { group: { type: 'and', calculations: [] } }; + return ( + + onChange({ ...rule, group })} /> + + ); +} diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/es-ES.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/components/Fieldset.tsx similarity index 52% rename from packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/es-ES.ts rename to packages/plugins/@nocobase/plugin-workflow/src/client/components/Fieldset.tsx index 6af3864802..078883b0a9 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-update/src/locale/es-ES.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/components/Fieldset.tsx @@ -7,8 +7,14 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -export default { - "Bulk update": "Actualización en masa", - "After successful bulk update": "Tras una actualización en masa correcta", - "Please select the records to be updated": "Seleccione los registros que desea actualizar", -}; +import React from 'react'; +import { FormLayout } from '@formily/antd-v5'; +import { Card } from 'antd'; + +export function Fieldset(props) { + return ( + + {props.children} + + ); +} diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/components/index.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/components/index.ts index e2653ed999..9781d56c8d 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/components/index.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/components/index.ts @@ -15,3 +15,6 @@ export * from './RadioWithTooltip'; export * from './CheckboxGroupWithTooltip'; export * from './ValueBlock'; export * from './SimpleDesigner'; +export * from './renderEngineReference'; +export * from './Calculation'; +export * from './Fieldset'; diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx index e083a69f43..1aec3ca660 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx @@ -32,7 +32,7 @@ export default class extends Instruction { options: getOptions(), }, required: true, - default: 'math.js', + default: 'formula.js', }, expression: { type: 'string', diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx index 1393e28650..c22382a470 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx @@ -7,13 +7,9 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { CloseCircleOutlined } from '@ant-design/icons'; -import { css, cx, useCompile, Variable } from '@nocobase/client'; import { evaluators } from '@nocobase/evaluators/client'; -import { Registry } from '@nocobase/utils/client'; -import { Button, Select } from 'antd'; -import React, { useCallback } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Instruction, NodeDefaultView } from '.'; import { Branch } from '../Branch'; import { RadioWithTooltip, RadioWithTooltipOption } from '../components/RadioWithTooltip'; @@ -22,333 +18,13 @@ import { useFlowContext } from '../FlowContext'; import { lang, NAMESPACE } from '../locale'; import useStyles from '../style'; import { useWorkflowVariableOptions, WorkflowVariableTextArea } from '../variable'; +import { CalculationConfig } from '../components/Calculation'; -interface Calculator { - name: string; - type: 'boolean' | 'number' | 'string' | 'date' | 'unknown' | 'null' | 'array'; - group: string; -} - -export const calculators = new Registry(); - -calculators.register('equal', { - name: '=', - type: 'boolean', - group: 'boolean', -}); -calculators.register('notEqual', { - name: '≠', - type: 'boolean', - group: 'boolean', -}); -calculators.register('gt', { - name: '>', - type: 'boolean', - group: 'boolean', -}); -calculators.register('gte', { - name: '≥', - type: 'boolean', - group: 'boolean', -}); -calculators.register('lt', { - name: '<', - type: 'boolean', - group: 'boolean', -}); -calculators.register('lte', { - name: '≤', - type: 'boolean', - group: 'boolean', -}); - -calculators.register('add', { - name: '+', - type: 'number', - group: 'number', -}); -calculators.register('minus', { - name: '-', - type: 'number', - group: 'number', -}); -calculators.register('multiple', { - name: '*', - type: 'number', - group: 'number', -}); -calculators.register('divide', { - name: '/', - type: 'number', - group: 'number', -}); -calculators.register('mod', { - name: '%', - type: 'number', - group: 'number', -}); - -calculators.register('includes', { - name: '{{t("contains")}}', - type: 'boolean', - group: 'string', -}); -calculators.register('notIncludes', { - name: '{{t("does not contain")}}', - type: 'boolean', - group: 'string', -}); -calculators.register('startsWith', { - name: '{{t("starts with")}}', - type: 'boolean', - group: 'string', -}); -calculators.register('notStartsWith', { - name: '{{t("not starts with")}}', - type: 'boolean', - group: 'string', -}); -calculators.register('endsWith', { - name: '{{t("ends with")}}', - type: 'boolean', - group: 'string', -}); -calculators.register('notEndsWith', { - name: '{{t("not ends with")}}', - type: 'boolean', - group: 'string', -}); -calculators.register('concat', { - name: `{{t("concat", { ns: "${NAMESPACE}" })}}`, - type: 'string', - group: 'string', -}); - -const calculatorGroups = [ - { - value: 'boolean', - title: '{{t("Comparision")}}', - }, - { - value: 'number', - title: `{{t("Arithmetic calculation", { ns: "${NAMESPACE}" })}}`, - }, - { - value: 'string', - title: `{{t("String operation", { ns: "${NAMESPACE}" })}}`, - }, - { - value: 'date', - title: `{{t("Date", { ns: "${NAMESPACE}" })}}`, - }, -]; - -function getGroupCalculators(group) { - return Array.from(calculators.getEntities()).filter(([key, value]) => value.group === group); -} - -function Calculation({ calculator, operands = [], onChange }) { - const compile = useCompile(); - const leftOptions = useWorkflowVariableOptions(); - const rightOptions = useWorkflowVariableOptions(); - const leftOperandOnChange = useCallback( - (v) => onChange({ calculator, operands: [v, operands[1]] }), - [calculator, onChange, operands], - ); - const rightOperandOnChange = useCallback( - (v) => onChange({ calculator, operands: [operands[0], v] }), - [calculator, onChange, operands], - ); - const operatorOnChange = useCallback((v) => onChange({ operands, calculator: v }), [onChange, operands]); - - return ( -
- - - -
- ); -} - -function CalculationItem({ value, onChange, onRemove }) { - if (!value) { - return null; - } - - const { calculator, operands = [] } = value; - - return ( -
- {value.group ? ( - onChange({ ...value, group })} /> - ) : ( - - )} -
- ); -} - -function CalculationGroup({ value, onChange }) { - const { t } = useTranslation(); - const { type = 'and', calculations = [] } = value; - - const onAddSingle = useCallback(() => { - onChange({ - ...value, - calculations: [...calculations, { not: false, calculator: 'equal' }], - }); - }, [value, calculations, onChange]); - - const onAddGroup = useCallback(() => { - onChange({ - ...value, - calculations: [...calculations, { not: false, group: { type: 'and', calculations: [] } }], - }); - }, [value, calculations, onChange]); - - const onRemove = useCallback( - (i: number) => { - calculations.splice(i, 1); - onChange({ ...value, calculations: [...calculations] }); - }, - [value, calculations, onChange], - ); - - const onItemChange = useCallback( - (i: number, v) => { - calculations.splice(i, 1, v); - - onChange({ ...value, calculations: [...calculations] }); - }, - [value, calculations, onChange], - ); - - return ( -
-
- - {'Meet '} - - {' conditions in the group'} - -
-
- {calculations.map((calculation, i) => ( - onRemove(i)} - /> - ))} -
-
- - -
-
- ); -} - -function CalculationConfig({ value, onChange }) { - const rule = value && Object.keys(value).length ? value : { group: { type: 'and', calculations: [] } }; - return onChange({ ...rule, group })} />; -} +const BRANCH_INDEX = { + DEFAULT: null, + ON_TRUE: 1, + ON_FALSE: 0, +} as const; export default class extends Instruction { title = `{{t("Condition", { ns: "${NAMESPACE}" })}}`; @@ -390,7 +66,7 @@ export default class extends Instruction { default: 'basic', }, calculation: { - type: 'string', + type: 'object', title: `{{t("Condition", { ns: "${NAMESPACE}" })}}`, 'x-decorator': 'FormItem', 'x-component': 'CalculationConfig', @@ -437,18 +113,44 @@ export default class extends Instruction { required: true, }, }; - options = [ - { - label: `{{t('Continue when "Yes"', { ns: "${NAMESPACE}" })}}`, - key: 'rejectOnFalse', - value: { rejectOnFalse: true }, + presetFieldset = { + rejectOnFalse: { + type: 'boolean', + title: `{{t("Mode", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'Radio.Group', + enum: [ + { + label: `{{t('Continue when "Yes"', { ns: "${NAMESPACE}" })}}`, + value: true, + }, + { + label: `{{t('Branch into "Yes" and "No"', { ns: "${NAMESPACE}" })}}`, + value: false, + }, + ], + default: true, }, - { - label: `{{t('Branch into "Yes" and "No"', { ns: "${NAMESPACE}" })}}`, - key: 'branch', - value: { rejectOnFalse: false }, - }, - ]; + }; + + branching = ({ rejectOnFalse = true } = {}) => { + return rejectOnFalse + ? false + : [ + { + label: `{{t('After end of branches', { ns: "${NAMESPACE}" })}}`, + value: false, + }, + { + label: `{{t('Inside of "Yes" branch', { ns: "${NAMESPACE}" })}}`, + value: BRANCH_INDEX.ON_TRUE, + }, + { + label: `{{t('Inside of "No" branch', { ns: "${NAMESPACE}" })}}`, + value: BRANCH_INDEX.ON_FALSE, + }, + ]; + }; scope = { renderEngineReference, diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx index f2c7acbd9b..ea6a2f168e 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx @@ -8,12 +8,12 @@ */ import { CloseOutlined, DeleteOutlined } from '@ant-design/icons'; -import { createForm } from '@formily/core'; +import { createForm, Field } from '@formily/core'; import { toJS } from '@formily/reactive'; -import { ISchema, useForm } from '@formily/react'; -import { Alert, App, Button, Dropdown, Input, Tag, Tooltip, message } from 'antd'; -import { cloneDeep, get } from 'lodash'; -import React, { useCallback, useContext, useMemo, useState } from 'react'; +import { ISchema, observer, useField, useForm } from '@formily/react'; +import { Alert, App, Button, Dropdown, Empty, Input, Space, Tag, Tooltip, message } from 'antd'; +import { cloneDeep, get, set } from 'lodash'; +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -21,10 +21,12 @@ import { FormProvider, SchemaComponent, SchemaInitializerItemType, + Variable, css, cx, useAPIClient, useActionContext, + useCancelAction, useCompile, usePlugin, useResourceActionContext, @@ -32,7 +34,7 @@ import { import { parse, str2moment } from '@nocobase/utils/client'; import WorkflowPlugin from '..'; -import { AddButton } from '../AddButton'; +import { AddButton } from '../AddNodeContext'; import { useFlowContext } from '../FlowContext'; import { DrawerDescription } from '../components/DrawerDescription'; import { StatusButton } from '../components/StatusButton'; @@ -40,7 +42,7 @@ import { JobStatusOptionsMap } from '../constants'; import { useGetAriaLabelOfAddButton } from '../hooks/useGetAriaLabelOfAddButton'; import { lang } from '../locale'; import useStyles from '../style'; -import { UseVariableOptions, VariableOption } from '../variable'; +import { UseVariableOptions, VariableOption, WorkflowVariableInput } from '../variable'; export type NodeAvailableContext = { engine: WorkflowPlugin; @@ -49,27 +51,40 @@ export type NodeAvailableContext = { branchIndex: number; }; +type Config = Record; + +type Options = { label: string; value: any }[]; + export abstract class Instruction { title: string; type: string; group: string; description?: string; /** - * @experimental + * @deprecated migrate to `presetFieldset` instead */ options?: { label: string; value: any; key: string }[]; - fieldset: { [key: string]: ISchema }; + fieldset: Record; + /** + * @experimental + */ + presetFieldset?: Record; + /** + * To presentation if the instruction is creating a branch + * @experimental + */ + branching?: boolean | Options | ((config: Config) => boolean | Options); /** * @experimental */ view?: ISchema; - scope?: { [key: string]: any }; - components?: { [key: string]: any }; + scope?: Record; + components?: Record; Component?(props): JSX.Element; /** * @experimental */ - createDefaultConfig?(): Record { + createDefaultConfig?(): Config { return {}; } useVariables?(node, options?: UseVariableOptions): VariableOption; @@ -80,6 +95,7 @@ export abstract class Instruction { */ isAvailable?(ctx: NodeAvailableContext): boolean; end?: boolean | ((node) => boolean); + testable?: boolean; } function useUpdateAction() { @@ -184,19 +200,16 @@ export function RemoveButton() { const current = useNodeContext(); const { modal } = App.useApp(); - if (!workflow) { - return null; - } const resource = api.resource('flow_nodes'); - async function onRemove() { - async function onOk() { - await resource.destroy?.({ - filterByTk: current.id, - }); - refresh(); - } + const onOk = useCallback(async () => { + await resource.destroy?.({ + filterByTk: current.id, + }); + refresh(); + }, [current.id, refresh, resource]); + const onRemove = useCallback(async () => { const usingNodes = nodes.filter((node) => { if (node === current) { return false; @@ -230,6 +243,10 @@ export function RemoveButton() { content: message, onOk, }); + }, [current, modal, nodes, onOk, t]); + + if (!workflow) { + return null; } return workflow.executed ? null : ( @@ -239,6 +256,7 @@ export function RemoveButton() { icon={} onClick={onRemove} className="workflow-node-remove-button" + size="small" /> ); } @@ -303,6 +321,194 @@ function useFormProviderProps() { return { form: useForm() }; } +const useRunAction = () => { + const { values, query } = useForm(); + const node = useNodeContext(); + const api = useAPIClient(); + const ctx = useActionContext(); + const field = useField(); + return { + async run() { + const template = parse(node.config); + const config = template(toJS(values.config)); + const resultField = query('result').take() as Field; + resultField.setValue(null); + resultField.setFeedback({}); + + field.data = field.data || {}; + field.data.loading = true; + + try { + const { + data: { data }, + } = await api.resource('flow_nodes').test({ + values: { + config, + type: node.type, + }, + }); + + resultField.setFeedback({ + type: data.status > 0 ? 'success' : 'error', + messages: data.status > 0 ? [lang('Resolved')] : [lang('Failed')], + }); + resultField.setValue(data.result); + } catch (err) { + resultField.setFeedback({ + type: 'error', + messages: err.message, + }); + } + field.data.loading = false; + ctx.setFormValueChanged(false); + }, + }; +}; + +const VariableKeysContext = createContext([]); + +function VariableReplacer({ name, value, onChange }) { + return ( + + + + + ); +} + +function TestFormFieldset({ value, onChange }) { + const keys = useContext(VariableKeysContext); + + return keys.length ? ( + <> + {keys.map((key) => ( + { + set(value, key, v); + onChange(value); + }} + /> + ))} + + ) : ( + + ); +} + +function TestButton() { + const node = useNodeContext(); + const { values } = useForm(); + const template = parse(values); + const keys = template.parameters.map((item) => item.key); + const form = useMemo(() => createForm(), []); + + return ( + + + + + + ); +} + export function NodeDefaultView(props) { const { data, children } = props; const compile = useCompile(); @@ -376,19 +582,18 @@ export function NodeDefaultView(props) { 'Node with unknown type will cause error. Please delete it or check plugin which provide this type.', )} > -
-
- {lang('Unknown node')} - {data.id} +
+
+
+ {lang('Unknown node')} + {data.id} +
+
+ + +
- -
@@ -405,9 +610,15 @@ export function NodeDefaultView(props) { className={cx(styles.nodeCardClass, { configuring: editingConfig })} onClick={onOpenDrawer} > -
- {typeTitle} - {data.id} +
+
+ {typeTitle} + {data.id} +
+
+ + +
onChangeTitle(ev.target.value)} autoSize /> - - { .workflow-node-remove-button { display: none; position: absolute; - right: 0.5em; - top: 0.5em; + right: 0; + top: 0; color: ${token.colorText}; &[disabled] { @@ -312,19 +312,24 @@ const useStyles = createStyles(({ css, token }) => { nodeJobButtonClass: css` display: flex; position: absolute; - top: calc(1em - 1px); - right: 1em; + top: 0; + right: 0; justify-content: center; align-items: center; color: ${token.colorTextLightSolid}; `, nodeHeaderClass: css` - position: relative; + display: flex; + margin-bottom: 0.5em; + + .workflow-node-actions { + position: relative; + } `, nodeMetaClass: css` - margin-bottom: 0.5em; + flex-grow: 1; .workflow-node-id { color: ${token.colorTextDescription}; @@ -352,7 +357,6 @@ const useStyles = createStyles(({ css, token }) => { `, nodeJobResultClass: css` - padding: 1em; background-color: #f3f3f3; `, diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/triggers/index.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/triggers/index.tsx index 358cc8cf81..5a6af1e0a0 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/triggers/index.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/triggers/index.tsx @@ -240,8 +240,10 @@ export const TriggerConfig = () => { className={cx(styles.nodeCardClass, 'invalid')} onClick={onOpenDrawer} > -
- {lang('Unknown trigger')} +
+
+ {lang('Unknown trigger')} +
@@ -260,11 +262,16 @@ export const TriggerConfig = () => { className={cx(styles.nodeCardClass)} onClick={onOpenDrawer} > -
- - - {compile(trigger.title)} - +
+
+ + + {compile(trigger.title)} + +
+
+ +
{ disabled={workflow.executed} />
- collectionManager.getCollectionFields(collectionName), [collectionManager]); + return useCallback((collectionName) => collectionManager.getCollectionAllFields(collectionName), [collectionManager]); } export function WorkflowVariableInput({ variableOptions, ...props }): JSX.Element { diff --git a/packages/plugins/@nocobase/plugin-workflow/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-workflow/src/locale/ja-JP.json index 65bccf5bb7..6aace7b789 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/locale/ja-JP.json +++ b/packages/plugins/@nocobase/plugin-workflow/src/locale/ja-JP.json @@ -1,14 +1,21 @@ { "Workflow": "ワークフロー", "Execution history": "実行履歴", + "Executed": "実行済み", "Trigger type": "トリガータイプ", "Status": "状態", "On": "有効", "Off": "無効", "Version": "バージョン", "Copy to new version": "新しいバージョンにコピー", + "Duplicate": "複製", + "Loading": "読み込み中", "Load failed": "読み込みに失敗しました", "Trigger": "トリガー", + "Trigger variables": "トリガーフィールド", + "Trigger data": "トリガーデータ", + "Trigger time": "トリガー時間", + "Triggered at": "トリガー発生時", "Collection event": "コレクションイベント", "Trigger on": "トリガータイミング", "After record added": "レコードを追加した後", @@ -24,6 +31,7 @@ "Based on date field of collection": "コレクションのフィールド時間に基づく", "Starts on": "開始", "Ends on": "終了", + "No end": "終了なし", "Exactly at": "ちょうど", "Repeat mode": "繰り返しパターン", "Repeat limit": "繰り返し回数", @@ -45,26 +53,32 @@ "By custom date": "カスタム時間", "Advanced": "アドバンスド", "End": "終了", - "Trigger variables": "トリガーフィールド", "Node result": "ノードの結果", "Constant": "定数", + "Null": "ヌル", "Boolean": "論理値", "String": "文字列", + "Operator": "演算子", "Arithmetic calculation": "算術演算", "String operation": "文字列操作", + "Executed at": "実行時刻", + "Queueing": "キュー中", "On going": "処理中", "Succeeded": "成功", "Failed": "失敗", + "Pending": "保留中", "Canceled": "取消", "This node contains branches, deleting will also be preformed to them, are you sure?": "ノードにはブランチが含まれており、そのすべてのブランチの下にあるすべての子ノードが同時に削除されます。続行してもよろしいですか?", "Control": "プロセス制御", "Collection operations": "フィールド操作", + "Extended types": "拡張タイプ", "Node type": "ノードタイプ", "Calculation": "演算", "Configure calculation": "演算設定", "Calculation result": "演算結果", - "True": "真", - "False": "偽", + "true": "真", + "false": "偽", + "concat": "連結", "Condition": "条件", "Mode": "モデル", "Continue when \"Yes\"": "「はい」の場合に続行", @@ -78,5 +92,100 @@ "Only update records matching conditions": "条件を満たすレコードのみ更新", "Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.": "値が割り当てられていないフィールドはデフォルト値に設定され、デフォルト値がないフィールドは null に設定されます", "Trigger in executed workflow cannot be modified": "すでにワークフローを実行したトリガーは変更できません", - "Node in executed workflow cannot be modified": "すでに実行されたワークフローのノードは変更できません" + "Node in executed workflow cannot be modified": "すでに実行されたワークフローのノードは変更できません", + "Can not delete": "削除できません", + "The result of this node has been referenced by other nodes ({{nodes}}), please remove the usage before deleting.": "このノードの結果は他のノードで参照されています({{nodes}})。削除する前に使用を解除してください。", + "Clear all executions": "すべての実行履歴を消去", + "Clear executions will not reset executed count, and started executions will not be deleted, are you sure you want to delete them all?": "実行履歴を消去しても、実行回数はリセットされず、実行中のものも削除されません。すべての実行履歴を削除してもよろしいですか?", + "Sync": "同期", + "Sync enabled status of all workflows from database": "データベースからすべてのワークフローの有効状態を同期します", + "Duplicate to new workflow": "新しいワークフローにコピー", + "Delete a main version will cause all other revisions to be deleted too.": "メインバージョンを削除すると、他のすべてのリビジョンも同時に削除されます。", + "Use transaction": "トランザクションを有効にする", + "Data operation nodes in workflow will run in a same transaction until any interruption. Any failure will cause data rollback, and will also rollback the history of the execution.": "ワークフロー内のノードは同じトランザクションで実行されます。失敗するとデータがロールバックされ、実行履歴もロールバックされます。", + "Auto delete history when execution is on end status": "実行が終了すると対応するステータスの履歴が自動的に削除されます。", + "Unknown trigger": "不明なトリガー", + "Workflow with unknown type will cause error. Please delete it or check plugin which provide this type.": "不明なタイプのワークフローはエラーを引き起こします。このタイプを提供するプラグインを削除するか確認してください。", + "Execute mode": "実行モード", + "Execute workflow asynchronously or synchronously based on trigger type, and could not be changed after created.": "トリガータイプに基づいてワークフローを非同期または同期的に実行し、作成後は変更できません。", + "Asynchronously": "非同期", + "Synchronously": "同期的に", + "Will be executed in the background as a queued task.": "キュータスクとしてバックグラウンドで実行されます。", + "For user actions that require immediate feedback. Can not use asynchronous nodes in such mode, and it is not recommended to perform time-consuming operations under synchronous mode.": "即時のフィードバックを必要とするユーザー操作に適しています。このモードでは非同期ノードを使用できず、同期モードで時間のかかる操作を実行することは推奨されません。", + "Go back": "戻る", + "Bind workflows": "ワークフローをバインドする", + "Workflow will be triggered before or after submitting succeeded based on workflow type (supports pre/post action event in local mode, and approval event).": "ワークフローは、送信が成功する前または後に、ワークフローの種類に基づいてトリガーされます(ローカルモードでのアクション前後のイベントと承認イベントをサポート)。", + "Workflow will be triggered directly once the button clicked, without data saving. Only supports to be bound with \"Custom action event\".": "ボタンをクリックするとデータを保存せずにワークフローが直接トリガーされます。「カスタムアクションイベント」にのみバインドできます。", + "Submit to workflow to \"Post-action event\" is deprecated, please use \"Custom action event\" instead.": "「ポストアクションイベント」にワークフローを送信することは廃止されましたので、「カスタムアクションイベント」を使用してください。", + "Workflow will be triggered before deleting succeeded (only supports pre-action event in local mode).": "削除が成功する前にワークフローがトリガーされます(ローカルモードではアクション前イベントのみサポート)。", + "Submit to workflow": "ワークフローに送信する", + "Add workflow": "ワークフローを追加", + "Select workflow": "ワークフローを選択", + "Trigger data context": "データコンテキストをトリガー", + "Full form data": "全てのフォームデータ", + "Select context": "コンテキストを選択", + "Triggered when data changes in the collection, such as after adding, updating, or deleting a record. Unlike \"Post-action event\", Collection event listens for data changes rather than HTTP requests. Unless you understand the exact meaning, it is recommended to use \"Post-action event\".": "コレクション内でデータが変更されたときにトリガーされます。たとえば、レコードの追加、更新、または削除後です。「ポストアクションイベント」とは異なり、コレクションイベントはHTTPリクエストではなくデータ変更を監視します。意味を理解していない限り、「ポストアクションイベント」の使用を推奨します。", + "Preload associations": "関連データを事前読み込み", + "Please select the associated fields that need to be accessed in subsequent nodes. With more than two levels of to-many associations may cause performance issue, please use with caution.": "次のノードでアクセスする必要がある関連フィールドを選択してください。二つ以上の多対多の関連はパフォーマンス問題を引き起こす可能性があるため、注意して使用してください。", + "Triggered according to preset time conditions. Suitable for one-time or periodic tasks, such as sending notifications and cleaning data on a schedule.": "あらかじめ設定された時間条件に従ってトリガーされます。通知の送信やデータの定期清掃など、一度限りまたは周期的なタスクに適しています。", + "Variable key of node": "ノードの変数キー", + "Scope variables": "スコープ変数", + "Calculate an expression based on a calculation engine and obtain a value as the result. Variables in the upstream nodes can be used in the expression.": "計算エンジンを使用して式を計算し、その結果として値を取得します。式には、上流ノードの変数を使用できます。", + "System variables": "システム変数", + "System time": "システム時間", + "Date variables": "日付変数", + "Date range": "日付範囲", + "Resolved": "解決済み", + "Error": "エラー", + "Aborted": "中止", + "Rejected": "拒否された", + "Retry needed": "再試行が必要", + "View result": "結果を表示", + "Triggered but still waiting in queue to execute.": "トリガーされましたが、まだキューに実行待ちの状態です。", + "Started and executing, maybe waiting for an async callback (manual, delay etc.).": "開始され、実行中です。非同期コールバック(手動、遅延など)を待っている可能性があります。", + "Successfully finished.": "正常に完了しました。", + "Failed to satisfy node configurations.": "ノード構成が満たされず失敗しました。", + "Some node meets error.": "いくつかのノードでエラーが発生しました。", + "Running of some node was aborted by program flow.": "プログラムフローにより、いくつかのノードが中止されました。", + "Manually canceled whole execution when waiting.": "待機中に手動で全体の実行がキャンセルされました。", + "Rejected from a manual node.": "手動ノードで拒否されました。", + "General failed but should do another try.": "一般的な失敗ですが、再試行が必要です。", + "Cancel the execution": "実行をキャンセル", + "Are you sure you want to cancel the execution?": "実行をキャンセルしてもよろしいですか?", + "Operations": "操作", + "Manual": "手動", + "Unknown node": "不明なノード", + "Node with unknown type will cause error. Please delete it or check plugin which provide this type.": "不明なタイプのノードはエラーを引き起こします。このタイプを提供するプラグインを削除するか確認してください。", + "Calculation engine": "計算エンジン", + "Basic": "基本", + "Calculation expression": "計算式", + "Expression syntax error": "式の文法エラー", + "Syntax references: ": "文法リファレンス:", + "Based on boolean result of the calculation to determine whether to \"continue\" or \"exit\" the process, or continue on different branches of \"yes\" and \"no\".": "計算結果の真偽に基づき、プロセスを「続行」または「終了」するか、「はい」と「いいえ」の異なる分岐で続行します。", + "Condition expression": "条件式", + "Add new record to a collection. You can use variables from upstream nodes to assign values to fields.": "コレクションに新しいレコードを追加します。上流ノードの変数を使用してフィールドに値を割り当てることができます。", + "Update records of a collection. You can use variables from upstream nodes as query conditions and field values.": "コレクションのレコードを更新します。上流ノードの変数を使用してクエリ条件やフィールド値を指定できます。", + "Update mode": "更新モード", + "Update in a batch": "一括更新", + "Update one by one": "個別更新", + "Update all eligible data at one time, which has better performance when the amount of data is large. But the updated data will not trigger other workflows, and will not record audit logs.": "対象データを一括で更新します。データ量が多い場合にパフォーマンスが向上します。ただし、更新されたデータは他のワークフローをトリガーせず、監査ログも記録しません。", + "The updated data can trigger other workflows, and the audit log will also be recorded. But it is usually only applicable to several or dozens of pieces of data, otherwise there will be performance problems.": "更新されたデータは他のワークフローをトリガーでき、監査ログも記録されます。ただし、通常は数件から数十件のデータにしか適用されず、それ以上の量ではパフォーマンスの問題が発生する可能性があります。", + "Query records from a collection. You can use variables from upstream nodes as query conditions.": "コレクションからレコードをクエリします。上流ノードの変数をクエリ条件として使用できます。", + "Allow multiple records as result": "複数のレコードを結果として許可", + "If checked, when there are multiple records in the query result, an array will be returned as the result, which can be operated on one by one using a loop node. Otherwise, only one record will be returned.": "チェックされた場合、クエリ結果に複数のレコードが含まれる場合、配列が結果として返されます。これをループノードで1つずつ処理できます。チェックされない場合、1つのレコードだけが返されます。", + "Result type": "結果タイプ", + "Single record": "単一レコード", + "The result will be an object of the first matching record only, or null if no matched record.": "結果は最初に一致したレコードのオブジェクトであり、一致するレコードがない場合はnullが返されます。", + "The result will be an array containing matched records, or an empty one if no matching records. This can be used to be processed in a loop node.": "結果は一致するレコードを含む配列となり、一致するレコードがない場合は空の配列が返されます。これをループノードで処理できます。", + "Exit when query result is null": "クエリ結果がnullの場合、終了する", + "Please add at least one condition": "少なくとも1つの条件を追加してください", + "Unassigned fields will be set to default values, and those without default values will be set to null.": "未割り当てのフィールドはデフォルト値に設定され、デフォルト値がない場合はnullに設定されます。", + "Delete record": "レコードを削除", + "Delete records of a collection. Could use variables in workflow context as filter. All records match the filter will be deleted.": "コレクション内のレコードを削除します。ワークフローのコンテキストで変数をフィルターとして使用できます。フィルター条件に一致するすべてのレコードが削除されます。", + "Executed workflow cannot be modified. Could be copied to a new version to modify.": "実行済みのワークフローは変更できません。新しいバージョンにコピーして変更できます。", + "End process": "プロセスを終了", + "End the process immediately, with set status.": "設定されたステータスでプロセスを即時終了します。", + "End status": "終了ステータス", + "True": "真", + "False": "偽" } diff --git a/packages/plugins/@nocobase/plugin-workflow/src/locale/ja_JP.json b/packages/plugins/@nocobase/plugin-workflow/src/locale/ja_JP.json deleted file mode 100644 index 7a4c54db30..0000000000 --- a/packages/plugins/@nocobase/plugin-workflow/src/locale/ja_JP.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "Workflow": "ワークフロー", - "Execution history": "実行履歴", - "Executed": "実行済み", - "Trigger type": "トリガータイプ", - "Status": "状態", - "On": "有効", - "Off": "無効", - "Version": "バージョン", - "Copy to new version": "新しいバージョンにコピー", - "Duplicate": "複製", - "Loading": "読み込み中", - "Load failed": "読み込みに失敗しました", - "Trigger": "トリガー", - "Trigger variables": "トリガー変数", - "Trigger data": "トリガーデータ", - "Trigger time": "トリガー時間", - "Triggered at": "トリガー発生時", - "Collection event": "コレクションイベント", - "Trigger on": "トリガータイミング", - "After record added": "レコード追加後", - "After record updated": "レコード更新後", - "After record added or updated": "レコード追加または更新後", - "After record deleted": "レコード削除後", - "Changed fields": "フィールドが変更された時", - "Triggered only if one of the selected fields changes. If unselected, it means that it will be triggered when any field changes. When record is added or deleted, any field is considered to have been changed.": "選択したフィールドのいずれかが変更された場合にのみトリガーされます。選択されていない場合、フィールドの変更があるとトリガーされます。レコードが追加または削除されると、すべてのフィールドが変更されたと見なされます。", - "Only triggers when match conditions": "以下の条件を満たすときにのみ発動", - "Schedule event": "スケジュールイベント", - "Trigger mode": "トリガーモード", - "Based on certain date": "カスタム日時", - "Based on date field of collection": "コレクションの日付フィールドに基づく", - "Starts on": "開始", - "Ends on": "終了", - "No end": "終了なし", - "Exactly at": "ちょうど", - "Repeat mode": "繰り返しモード", - "Repeat limit": "繰り返し回数", - "No limit": "無制限", - "Seconds": "秒", - "Minutes": "分", - "Hours": "時", - "Days": "日", - "Weeks": "週", - "Months": "月", - "No repeat": "繰り返しなし", - "Every": "毎", - "By minute": "分ごと", - "By hour": "時間ごと", - "By day": "日ごと", - "By week": "週ごと", - "By month": "月ごと", - "By field": "フィールドごと", - "By custom date": "カスタム日時", - "Advanced": "アドバンスド", - "End": "終了", - "Node result": "ノードの結果", - "Constant": "定数", - "Null": "ヌル", - "Boolean": "論理値", - "String": "文字列", - "Operator": "演算子", - "Arithmetic calculation": "算術演算", - "String operation": "文字列操作", - "Executed at": "実行時刻", - "Queueing": "キュー中", - "On going": "進行中", - "Succeeded": "成功", - "Failed": "失敗", - "Pending": "保留中", - "Canceled": "キャンセル", - "This node contains branches, deleting will also be preformed to them, are you sure?": "ノードにはブランチが含まれており、そのすべてのブランチの下にあるすべての子ノードが同時に削除されます。続行してもよろしいですか?", - "Control": "プロセス制御", - "Collection operations": "コレクション操作", - "Extended types": "拡張タイプ", - "Node type": "ノードタイプ", - "Calculation": "演算", - "Configure calculation": "演算設定", - "Calculation result": "演算結果", - "true": "真", - "false": "偽", - "concat": "連結", - "Condition": "条件", - "Mode": "モード", - "Continue when \"Yes\"": "「はい」の場合に続行", - "Branch into \"Yes\" and \"No\"": "「はい」と「いいえ」で分岐して続行", - "Conditions": "条件設定", - "Create record": "レコード追加", - "Update record": "レコード更新", - "Query record": "クエリ レコード", - "Multiple records": "複数レコード", - "Please select collection first": "先にコレクションを選択してください", - "Only update records matching conditions": "条件を満たすレコードのみ更新", - "Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.": "値が割り当てられていないフィールドはデフォルト値に設定され、デフォルト値がないフィールドは null に設定されます", - "Trigger in executed workflow cannot be modified": "すでにワークフローを実行したトリガーは変更できません", - "Node in executed workflow cannot be modified": "すでに実行されたワークフローのノードは変更できません", - "Can not delete": "削除できません", - "The result of this node has been referenced by other nodes ({{nodes}}), please remove the usage before deleting.": "このノードの結果は他のノードで参照されています({{nodes}})。削除する前に使用を解除してください。", - "Clear all executions": "すべての実行履歴を消去", - "Clear executions will not reset executed count, and started executions will not be deleted, are you sure you want to delete them all?": "実行履歴を消去しても、実行回数はリセットされず、実行中のものも削除されません。すべての実行履歴を削除してもよろしいですか?", - "Sync": "同期", - "Sync enabled status of all workflows from database": "データベースからすべてのワークフローの有効状態を同期します", - "Duplicate to new workflow": "新しいワークフローにコピー", - "Delete a main version will cause all other revisions to be deleted too.": "メインバージョンを削除すると、他のすべてのリビジョンも同時に削除されます。", - "Use transaction": "トランザクションを有効にする", - "Data operation nodes in workflow will run in a same transaction until any interruption. Any failure will cause data rollback, and will also rollback the history of the execution.": "ワークフロー内のノードは同じトランザクションで実行されます。失敗するとデータがロールバックされ、実行履歴もロールバックされます。", - "Auto delete history when execution is on end status": "実行が終了すると対応するステータスの履歴が自動的に削除されます。", - "Unknown trigger": "不明なトリガー", - "Workflow with unknown type will cause error. Please delete it or check plugin which provide this type.": "不明なタイプのワークフローはエラーを引き起こします。このタイプを提供するプラグインを削除するか確認してください。", - "Execute mode": "実行モード", - "Execute workflow asynchronously or synchronously based on trigger type, and could not be changed after created.": "トリガータイプに基づいてワークフローを非同期または同期的に実行し、作成後は変更できません。", - "Asynchronously": "非同期", - "Synchronously": "同期的に", - "Will be executed in the background as a queued task.": "キュータスクとしてバックグラウンドで実行されます。", - "For user actions that require immediate feedback. Can not use asynchronous nodes in such mode, and it is not recommended to perform time-consuming operations under synchronous mode.": "即時のフィードバックを必要とするユーザー操作に適しています。このモードでは非同期ノードを使用できず、同期モードで時間のかかる操作を実行することは推奨されません。", - "Go back": "戻る", - "Bind workflows": "ワークフローをバインドする", - "Workflow will be triggered before or after submitting succeeded based on workflow type (supports pre/post action event in local mode, and approval event).": "ワークフローは、送信が成功する前または後に、ワークフローの種類に基づいてトリガーされます(ローカルモードでのアクション前後のイベントと承認イベントをサポート)。", - "Workflow will be triggered directly once the button clicked, without data saving. Only supports to be bound with \"Custom action event\".": "ボタンをクリックするとデータを保存せずにワークフローが直接トリガーされます。「カスタムアクションイベント」にのみバインドできます。", - "Submit to workflow to \"Post-action event\" is deprecated, please use \"Custom action event\" instead.": "「ポストアクションイベント」にワークフローを送信することは廃止されましたので、「カスタムアクションイベント」を使用してください。", - "Workflow will be triggered before deleting succeeded (only supports pre-action event in local mode).": "削除が成功する前にワークフローがトリガーされます(ローカルモードではアクション前イベントのみサポート)。", - "Submit to workflow": "ワークフローに送信する", - "Add workflow": "ワークフローを追加", - "Select workflow": "ワークフローを選択", - "Trigger data context": "データコンテキストをトリガー", - "Full form data": "全てのフォームデータ", - "Select context": "コンテキストを選択", - "Triggered when data changes in the collection, such as after adding, updating, or deleting a record. Unlike \"Post-action event\", Collection event listens for data changes rather than HTTP requests. Unless you understand the exact meaning, it is recommended to use \"Post-action event\".": "コレクション内でデータが変更されたときにトリガーされます。たとえば、レコードの追加、更新、または削除後です。「ポストアクションイベント」とは異なり、コレクションイベントはHTTPリクエストではなくデータ変更を監視します。意味を理解していない限り、「ポストアクションイベント」の使用を推奨します。", - "Preload associations": "関連データを事前読み込み", - "Please select the associated fields that need to be accessed in subsequent nodes. With more than two levels of to-many associations may cause performance issue, please use with caution.": "次のノードでアクセスする必要がある関連フィールドを選択してください。二つ以上の多対多の関連はパフォーマンス問題を引き起こす可能性があるため、注意して使用してください。", - "Triggered according to preset time conditions. Suitable for one-time or periodic tasks, such as sending notifications and cleaning data on a schedule.": "あらかじめ設定された時間条件に従ってトリガーされます。通知の送信やデータの定期清掃など、一度限りまたは周期的なタスクに適しています。", - "Variable key of node": "ノードの変数キー", - "Scope variables": "スコープ変数", - "Calculate an expression based on a calculation engine and obtain a value as the result. Variables in the upstream nodes can be used in the expression.": "計算エンジンを使用して式を計算し、その結果として値を取得します。式には、上流ノードの変数を使用できます。", - "System variables": "システム変数", - "System time": "システム時間", - "Date variables": "日付変数", - "Date range": "日付範囲", - "Resolved": "解決済み", - "Error": "エラー", - "Aborted": "中止", - "Rejected": "拒否された", - "Retry needed": "再試行が必要", - "View result": "結果を表示", - "Triggered but still waiting in queue to execute.": "トリガーされましたが、まだキューに実行待ちの状態です。", - "Started and executing, maybe waiting for an async callback (manual, delay etc.).": "開始され、実行中です。非同期コールバック(手動、遅延など)を待っている可能性があります。", - "Successfully finished.": "正常に完了しました。", - "Failed to satisfy node configurations.": "ノード構成が満たされず失敗しました。", - "Some node meets error.": "いくつかのノードでエラーが発生しました。", - "Running of some node was aborted by program flow.": "プログラムフローにより、いくつかのノードが中止されました。", - "Manually canceled whole execution when waiting.": "待機中に手動で全体の実行がキャンセルされました。", - "Rejected from a manual node.": "手動ノードで拒否されました。", - "General failed but should do another try.": "一般的な失敗ですが、再試行が必要です。", - "Cancel the execution": "実行をキャンセル", - "Are you sure you want to cancel the execution?": "実行をキャンセルしてもよろしいですか?", - "Operations": "操作", - "Manual": "手動", - "Unknown node": "不明なノード", - "Node with unknown type will cause error. Please delete it or check plugin which provide this type.": "不明なタイプのノードはエラーを引き起こします。このタイプを提供するプラグインを削除するか確認してください。", - "Calculation engine": "計算エンジン", - "Basic": "基本", - "Calculation expression": "計算式", - "Expression syntax error": "式の文法エラー", - "Syntax references: ": "文法リファレンス:", - "Based on boolean result of the calculation to determine whether to \"continue\" or \"exit\" the process, or continue on different branches of \"yes\" and \"no\".": "計算結果の真偽に基づき、プロセスを「続行」または「終了」するか、「はい」と「いいえ」の異なる分岐で続行します。", - "Condition expression": "条件式", - "Add new record to a collection. You can use variables from upstream nodes to assign values to fields.": "コレクションに新しいレコードを追加します。上流ノードの変数を使用してフィールドに値を割り当てることができます。", - "Update records of a collection. You can use variables from upstream nodes as query conditions and field values.": "コレクションのレコードを更新します。上流ノードの変数を使用してクエリ条件やフィールド値を指定できます。", - "Update mode": "更新モード", - "Update in a batch": "一括更新", - "Update one by one": "個別更新", - "Update all eligible data at one time, which has better performance when the amount of data is large. But the updated data will not trigger other workflows, and will not record audit logs.": "対象データを一括で更新します。データ量が多い場合にパフォーマンスが向上します。ただし、更新されたデータは他のワークフローをトリガーせず、監査ログも記録しません。", - "The updated data can trigger other workflows, and the audit log will also be recorded. But it is usually only applicable to several or dozens of pieces of data, otherwise there will be performance problems.": "更新されたデータは他のワークフローをトリガーでき、監査ログも記録されます。ただし、通常は数件から数十件のデータにしか適用されず、それ以上の量ではパフォーマンスの問題が発生する可能性があります。", - "Query records from a collection. You can use variables from upstream nodes as query conditions.": "コレクションからレコードをクエリします。上流ノードの変数をクエリ条件として使用できます。", - "Allow multiple records as result": "複数のレコードを結果として許可", - "If checked, when there are multiple records in the query result, an array will be returned as the result, which can be operated on one by one using a loop node. Otherwise, only one record will be returned.": "チェックされた場合、クエリ結果に複数のレコードが含まれる場合、配列が結果として返されます。これをループノードで1つずつ処理できます。チェックされない場合、1つのレコードだけが返されます。", - "Result type": "結果タイプ", - "Single record": "単一レコード", - "The result will be an object of the first matching record only, or null if no matched record.": "結果は最初に一致したレコードのオブジェクトであり、一致するレコードがない場合はnullが返されます。", - "The result will be an array containing matched records, or an empty one if no matching records. This can be used to be processed in a loop node.": "結果は一致するレコードを含む配列となり、一致するレコードがない場合は空の配列が返されます。これをループノードで処理できます。", - "Exit when query result is null": "クエリ結果がnullの場合、終了する", - "Please add at least one condition": "少なくとも1つの条件を追加してください", - "Unassigned fields will be set to default values, and those without default values will be set to null.": "未割り当てのフィールドはデフォルト値に設定され、デフォルト値がない場合はnullに設定されます。", - "Delete record": "レコードを削除", - "Delete records of a collection. Could use variables in workflow context as filter. All records match the filter will be deleted.": "コレクション内のレコードを削除します。ワークフローのコンテキストで変数をフィルターとして使用できます。フィルター条件に一致するすべてのレコードが削除されます。", - "Executed workflow cannot be modified. Could be copied to a new version to modify.": "実行済みのワークフローは変更できません。新しいバージョンにコピーして変更できます。", - "End process": "プロセスを終了", - "End the process immediately, with set status.": "設定されたステータスでプロセスを即時終了します。", - "End status": "終了ステータス" -} diff --git a/packages/plugins/@nocobase/plugin-workflow/src/locale/ko_KR.json b/packages/plugins/@nocobase/plugin-workflow/src/locale/ko-KR.json similarity index 100% rename from packages/plugins/@nocobase/plugin-workflow/src/locale/ko_KR.json rename to packages/plugins/@nocobase/plugin-workflow/src/locale/ko-KR.json diff --git a/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json index ed7565f3e2..43baf66a8c 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-workflow/src/locale/zh-CN.json @@ -39,7 +39,7 @@ "适用于需要即时反馈的用户操作。不能在此模式下使用异步节点,并且不建议在同步模式下执行耗时的操作。", "Go back": "返回", "Bind workflows": "绑定工作流", - "Workflow will be triggered before or after submitting succeeded based on workflow type (supports pre/post action event in local mode, and approval event).": "工作流会基于其类型在提交成功之前或之后触发(支持局部模式的操作前、操作后事件,和审批事件)。", + "Support pre-action event (local mode), post-action event (local mode), and approval event here.": "此处支持“操作前事件(局部模式)”、“操作后事件(局部模式)”、“审批事件”。", "Workflow will be triggered directly once the button clicked, without data saving. Only supports to be bound with \"Custom action event\".": "按钮点击后直接触发工作流,但不会保存数据。仅支持绑定“自定义操作事件”。", "\"Submit to workflow\" to \"Post-action event\" is deprecated, please use \"Custom action event\" instead.": "“提交至工作流”到“操作后事件”的方式已被弃用,请使用“自定义操作事件”代替。", "Workflow will be triggered before deleting succeeded (only supports pre-action event in local mode).": "删除成功之前触发工作流(支持操作前事件)。", @@ -160,6 +160,8 @@ "Continue when \"Yes\"": "“是”则继续", "Branch into \"Yes\" and \"No\"": "“是”和“否”分别继续", "Condition expression": "条件表达式", + "Inside of \"Yes\" branch": "“是”分支内", + "Inside of \"No\" branch": "“否”分支内", "Create record": "新增数据", "Add new record to a collection. You can use variables from upstream nodes to assign values to fields.": "向一个数据表中添加新的数据。可以使用上游节点里的变量为字段赋值。", @@ -169,8 +171,8 @@ "Update mode": "更新模式", "Update in a batch": "批量更新", "Update one by one": "逐条更新", - "Update all eligible data at one time, which has better performance when the amount of data is large. But the updated data will not trigger other workflows, and will not record audit logs.": - "一次性更新所有符合条件的数据,在数据量较大时有比较好的性能;但被更新的数据不会触发其他工作流,也不会记录更新日志。", + "Update all eligible data at one time, which has better performance when the amount of data is large. But association fields are not supported (unless foreign key in current collection), and the updated data will not trigger other workflows.": + "一次性更新所有符合条件的数据,在数据量较大时有比较好的性能;但不支持关系字段的更新(除非是在当前表中的外键),被更新的数据也不会触发其他工作流。", "The updated data can trigger other workflows, and the audit log will also be recorded. But it is usually only applicable to several or dozens of pieces of data, otherwise there will be performance problems.": "被更新的数据可以再次触发其他工作流,也会记录更新日志;但通常只适用于数条或数十条数据,否则会有性能问题。", "Query record": "查询数据", @@ -203,5 +205,13 @@ "End process": "结束流程", "End the process immediately, with set status.": "以设置的状态立即结束流程。", "End status": "结束状态", - "Succeeded": "成功" + "Succeeded": "成功", + "Test run": "测试执行", + "Test run will do the actual data manipulating or API calling, please use with caution.": "测试执行会进行实际的数据操作或 API 调用,请谨慎使用。", + "No variable": "无变量", + + "Add node": "添加节点", + "Move all downstream nodes to": "将所有下游节点移至", + "After end of branches": "分支结束后", + "Inside of branch": "分支内" } diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts index fa4092a869..c39824ed97 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/Plugin.ts @@ -249,6 +249,7 @@ export default class PluginWorkflowServer extends Plugin { 'executions:destroy', 'flow_nodes:update', 'flow_nodes:destroy', + 'flow_nodes:test', ], }); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/Processor.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/Processor.ts index 420f7e0868..85909d06bf 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/Processor.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/Processor.ts @@ -259,10 +259,8 @@ export default class Processor { // TODO(optimize) /** * @experimental - * @param {JobModel | Record} payload - * @returns {JobModel} */ - async saveJob(payload) { + async saveJob(payload: JobModel | Record): Promise { const { database } = this.execution.constructor; const { transaction } = this; const { model } = database.getCollection('jobs'); @@ -377,7 +375,7 @@ export default class Processor { /** * @experimental */ - public getScope(sourceNodeId: number) { + public getScope(sourceNodeId: number, includeSelfScope = false) { const node = this.nodesMap.get(sourceNodeId); const systemFns = {}; const scope = { @@ -389,9 +387,9 @@ export default class Processor { } const $scopes = {}; - for (let n = this.findBranchParentNode(node); n; n = this.findBranchParentNode(n)) { + for (let n = includeSelfScope ? node : this.findBranchParentNode(node); n; n = this.findBranchParentNode(n)) { const instruction = this.options.plugin.instructions.get(n.type); - if (typeof instruction.getScope === 'function') { + if (typeof instruction?.getScope === 'function') { $scopes[n.id] = $scopes[n.key] = instruction.getScope(n, this.jobsMapByNodeKey[n.key], this); } } @@ -407,9 +405,9 @@ export default class Processor { /** * @experimental */ - public getParsedValue(value, sourceNodeId: number, additionalScope?: object) { + public getParsedValue(value, sourceNodeId: number, { additionalScope = {}, includeSelfScope = false } = {}) { const template = parse(value); - const scope = Object.assign(this.getScope(sourceNodeId), additionalScope); + const scope = Object.assign(this.getScope(sourceNodeId, includeSelfScope), additionalScope); template.parameters.forEach(({ key }) => { appendArrayColumn(scope, key); }); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/actions/nodes.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/actions/nodes.test.ts index 22ac417ea0..0bb667c551 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/actions/nodes.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/actions/nodes.test.ts @@ -10,6 +10,7 @@ import { MockServer } from '@nocobase/test'; import Database from '@nocobase/database'; import { getApp, sleep } from '@nocobase/plugin-workflow-test'; +import { JOB_STATUS } from '../../constants'; describe('workflow > actions > workflows', () => { let app: MockServer; @@ -244,4 +245,32 @@ describe('workflow > actions > workflows', () => { expect(nodes.length).toBe(0); }); }); + + describe('test', () => { + it('test method not implemented', async () => { + const { status } = await agent.resource('flow_nodes').test({ values: { type: 'error' } }); + + expect(status).toBe(400); + }); + + it('test method implemented', async () => { + const { + status, + body: { data }, + } = await agent.resource('flow_nodes').test({ values: { type: 'echo' } }); + + expect(status).toBe(200); + expect(data.status).toBe(JOB_STATUS.RESOLVED); + }); + + it('test with pending status', async () => { + const { + status, + body: { data }, + } = await agent.resource('flow_nodes').test({ values: { type: 'pending' } }); + + expect(status).toBe(200); + expect(data.status).toBe(JOB_STATUS.PENDING); + }); + }); }); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/actions/index.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/actions/index.ts index 2c4d9e1bbc..028ef3b1e1 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/actions/index.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/actions/index.ts @@ -22,7 +22,7 @@ function make(name, mod) { } export default function ({ app }) { - app.actions({ + app.resourceManager.registerActionHandlers({ ...make('workflows', workflows), ...make('workflows.nodes', { create: nodes.create, @@ -30,6 +30,7 @@ export default function ({ app }) { ...make('flow_nodes', { update: nodes.update, destroy: nodes.destroy, + test: nodes.test, }), ...make('executions', executions), }); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/actions/nodes.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/actions/nodes.ts index 1c635b0606..1cffbd2dbd 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/actions/nodes.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/actions/nodes.ts @@ -9,6 +9,7 @@ import { Context, utils } from '@nocobase/actions'; import { MultipleRelationRepository, Op, Repository } from '@nocobase/database'; +import WorkflowPlugin from '..'; import type { WorkflowModel } from '../types'; export async function create(context: Context, next) { @@ -219,3 +220,23 @@ export async function update(context: Context, next) { await next(); } + +export async function test(context: Context, next) { + const { values = {} } = context.action.params; + const { type, config = {} } = values; + const plugin = context.app.pm.get(WorkflowPlugin) as WorkflowPlugin; + const instruction = plugin.instructions.get(type); + if (!instruction) { + context.throw(400, `instruction "${type}" not registered`); + } + if (typeof instruction.test !== 'function') { + context.throw(400, `test method of instruction "${type}" not implemented`); + } + try { + context.body = await instruction.test(config); + } catch (error) { + context.throw(500, error.message); + } + + next(); +} diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/index.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/index.ts index d7baac1bf2..0e679da7ff 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/index.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/index.ts @@ -11,6 +11,7 @@ export * from './utils'; export * from './constants'; export * from './instructions'; export * from './functions'; +export * from './logicCalculate'; export { Trigger } from './triggers'; export { default as Processor } from './Processor'; export { default } from './Plugin'; diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts index ab40352377..f9eaecd846 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts @@ -8,13 +8,11 @@ */ import { evaluators } from '@nocobase/evaluators'; -import { Registry } from '@nocobase/utils'; import { Instruction } from '.'; import type Processor from '../Processor'; import { JOB_STATUS } from '../constants'; import type { FlowNodeModel, JobModel } from '../types'; - -type Comparer = (a: any, b: any) => boolean; +import { logicCalculate } from '../logicCalculate'; export const BRANCH_INDEX = { DEFAULT: null, @@ -22,115 +20,6 @@ export const BRANCH_INDEX = { ON_FALSE: 0, } as const; -export const calculators = new Registry(); - -// built-in functions -function equal(a, b) { - return a == b; -} - -function notEqual(a, b) { - return a != b; -} - -function gt(a, b) { - return a > b; -} - -function gte(a, b) { - return a >= b; -} - -function lt(a, b) { - return a < b; -} - -function lte(a, b) { - return a <= b; -} - -calculators.register('equal', equal); -calculators.register('notEqual', notEqual); -calculators.register('gt', gt); -calculators.register('gte', gte); -calculators.register('lt', lt); -calculators.register('lte', lte); - -calculators.register('==', equal); -calculators.register('!=', notEqual); -calculators.register('>', gt); -calculators.register('>=', gte); -calculators.register('<', lt); -calculators.register('<=', lte); - -function includes(a, b) { - return a.includes(b); -} - -function notIncludes(a, b) { - return !a.includes(b); -} - -function startsWith(a: string, b: string) { - return a.startsWith(b); -} - -function notStartsWith(a: string, b: string) { - return !a.startsWith(b); -} - -function endsWith(a: string, b: string) { - return a.endsWith(b); -} - -function notEndsWith(a: string, b: string) { - return !a.endsWith(b); -} - -calculators.register('includes', includes); -calculators.register('notIncludes', notIncludes); -calculators.register('startsWith', startsWith); -calculators.register('notStartsWith', notStartsWith); -calculators.register('endsWith', endsWith); -calculators.register('notEndsWith', notEndsWith); - -type CalculationItem = { - calculator?: string; - operands?: [any?, any?]; -}; - -type CalculationGroup = { - group: { - type: 'and' | 'or'; - calculations?: Calculation[]; - }; -}; - -type Calculation = CalculationItem | CalculationGroup; - -function calculate(calculation: CalculationItem = {}) { - type NewType = Comparer; - - let fn: NewType; - if (!(calculation.calculator && (fn = calculators.get(calculation.calculator)))) { - throw new Error(`no calculator function registered for "${calculation.calculator}"`); - } - return Boolean(fn(...(calculation.operands ?? []))); -} - -function logicCalculate(calculation?: Calculation) { - if (!calculation) { - return true; - } - - if (typeof calculation['group'] === 'object') { - const method = calculation['group'].type === 'and' ? 'every' : 'some'; - return (calculation['group'].calculations ?? [])[method]((item: Calculation) => logicCalculate(item)); - } - - return calculate(calculation as CalculationItem); -} - export class ConditionInstruction extends Instruction { async run(node: FlowNodeModel, prevJob, processor: Processor) { const { engine, calculation, expression, rejectOnFalse } = node.config || {}; diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/index.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/index.ts index bd208b4ce0..ddea1cc15e 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/index.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/index.ts @@ -29,6 +29,7 @@ export type InstructionInterface = { resume?: Runner; getScope?: (node: FlowNodeModel, data: any, processor: Processor) => any; duplicateConfig?: (node: FlowNodeModel, options: Transactionable) => object | Promise; + test?: (config: Record) => IJob | Promise; }; // what should a instruction do? diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/logicCalculate.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/logicCalculate.ts new file mode 100644 index 0000000000..737ed33596 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/logicCalculate.ts @@ -0,0 +1,127 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Registry } from '@nocobase/utils'; + +type Comparer = (a: any, b: any) => boolean; + +export const calculators = new Registry(); + +// built-in functions +function equal(a, b) { + return a == b; +} + +function notEqual(a, b) { + return a != b; +} + +function gt(a, b) { + return a > b; +} + +function gte(a, b) { + return a >= b; +} + +function lt(a, b) { + return a < b; +} + +function lte(a, b) { + return a <= b; +} + +calculators.register('equal', equal); +calculators.register('notEqual', notEqual); +calculators.register('gt', gt); +calculators.register('gte', gte); +calculators.register('lt', lt); +calculators.register('lte', lte); + +calculators.register('==', equal); +calculators.register('!=', notEqual); +calculators.register('>', gt); +calculators.register('>=', gte); +calculators.register('<', lt); +calculators.register('<=', lte); + +function includes(a, b) { + return a.includes(b); +} + +function notIncludes(a, b) { + return !a.includes(b); +} + +function startsWith(a: string, b: string) { + return a.startsWith(b); +} + +function notStartsWith(a: string, b: string) { + return !a.startsWith(b); +} + +function endsWith(a: string, b: string) { + return a.endsWith(b); +} + +function notEndsWith(a: string, b: string) { + return !a.endsWith(b); +} + +calculators.register('includes', includes); +calculators.register('notIncludes', notIncludes); +calculators.register('startsWith', startsWith); +calculators.register('notStartsWith', notStartsWith); +calculators.register('endsWith', endsWith); +calculators.register('notEndsWith', notEndsWith); + +type CalculationItem = { + calculator?: string; + operands?: [any?, any?]; +}; + +type CalculationGroup = { + group: { + type: 'and' | 'or'; + calculations?: Calculation[]; + }; +}; + +type Calculation = CalculationItem | CalculationGroup; + +function calculate(calculation: CalculationItem = {}): boolean { + let fn: Comparer; + if (!calculation.calculator || !calculation.operands?.length) { + return true; + } + if (!(fn = calculators.get(calculation.calculator))) { + throw new Error(`no calculator function registered for "${calculation.calculator}"`); + } + return Boolean(fn(...(calculation.operands ?? []))); +} + +const GroupTypeMethodMap = { + and: 'every', + or: 'some', +}; + +export function logicCalculate(calculation?: Calculation) { + if (!calculation) { + return true; + } + + if (typeof calculation['group'] === 'object') { + const method = GroupTypeMethodMap[calculation['group'].type]; + return (calculation['group'].calculations ?? [])[method]((item: Calculation) => logicCalculate(item)); + } + + return calculate(calculation as CalculationItem); +} diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/triggers/CollectionTrigger.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/triggers/CollectionTrigger.ts index 7b5487a184..eab4315815 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/triggers/CollectionTrigger.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/triggers/CollectionTrigger.ts @@ -7,12 +7,13 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Model, Transactionable } from '@nocobase/database'; +import { Collection, Model, Transactionable } from '@nocobase/database'; import Trigger from '.'; import { toJSON } from '../utils'; import type { WorkflowModel } from '../types'; -import { ICollection, parseCollectionName } from '@nocobase/data-source-manager'; +import { ICollection, parseCollectionName, SequelizeCollectionManager } from '@nocobase/data-source-manager'; import { isValidFilter } from '@nocobase/utils'; +import { pick } from 'lodash'; export interface CollectionChangeTriggerConfig { collection: string; @@ -48,9 +49,8 @@ function getFieldRawName(collection: ICollection, name: string) { async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: Model, options) { const { condition, changed, mode, appends } = workflow.config; const [dataSourceName, collectionName] = parseCollectionName(workflow.config.collection); - const collection = this.workflow.app.dataSourceManager?.dataSources - .get(dataSourceName) - .collectionManager.getCollection(collectionName); + const { collectionManager } = this.workflow.app.dataSourceManager.dataSources.get(dataSourceName); + const collection: Collection = (collectionManager as SequelizeCollectionManager).getCollection(collectionName); const { transaction, context } = options; const { repository, filterTargetKey } = collection; @@ -68,14 +68,16 @@ async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: M return; } + const filterByTk = Array.isArray(filterTargetKey) + ? pick(data, filterTargetKey) + : { [filterTargetKey]: data[filterTargetKey] }; // NOTE: if no configured condition, or not match, do not trigger if (isValidFilter(condition) && !(mode & MODE_BITMAP.DESTROY)) { // TODO: change to map filter format to calculation format // const calculation = toCalculation(condition); const count = await repository.count({ - filter: { - $and: [condition, { [filterTargetKey]: data[filterTargetKey] }], - }, + filterByTk, + filter: condition, context, transaction, }); @@ -96,7 +98,7 @@ async function handler(this: CollectionTrigger, workflow: WorkflowModel, data: M // @ts-ignore result = await repository.findOne({ - filterByTk: data[filterTargetKey], + filterByTk, appends: Array.from(includeFields), transaction, }); diff --git a/packages/presets/nocobase/README.md b/packages/presets/nocobase/README.md new file mode 100644 index 0000000000..d3e8120f6f --- /dev/null +++ b/packages/presets/nocobase/README.md @@ -0,0 +1,30 @@ +# NocoBase + + + + +## What is NocoBase + +NocoBase is a scalability-first, open-source no-code development platform. +Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform! + +Homepage: +https://www.nocobase.com/ + +Online Demo: +https://demo.nocobase.com/new + +Documents: +https://docs.nocobase.com/ + +Commericial license & plugins: +https://www.nocobase.com/en/commercial + +License agreement: +https://www.nocobase.com/en/agreement + + +## Contact Us: +hello@nocobase.com \ No newline at end of file diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index 9ee3550df3..dcc2165106 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -1,76 +1,77 @@ { "name": "@nocobase/preset-nocobase", - "version": "1.4.0-alpha", + "version": "1.4.0-alpha.11", "license": "AGPL-3.0", "main": "./lib/server/index.js", "dependencies": { "@formily/json-schema": "2.x", - "@nocobase/plugin-acl": "1.4.0-alpha", - "@nocobase/plugin-action-bulk-edit": "1.4.0-alpha", - "@nocobase/plugin-action-bulk-update": "1.4.0-alpha", - "@nocobase/plugin-action-custom-request": "1.4.0-alpha", - "@nocobase/plugin-action-duplicate": "1.4.0-alpha", - "@nocobase/plugin-action-export": "1.4.0-alpha", - "@nocobase/plugin-action-import": "1.4.0-alpha", - "@nocobase/plugin-action-print": "1.4.0-alpha", - "@nocobase/plugin-api-doc": "1.4.0-alpha", - "@nocobase/plugin-api-keys": "1.4.0-alpha", - "@nocobase/plugin-audit-logs": "1.4.0-alpha", - "@nocobase/plugin-auth": "1.4.0-alpha", - "@nocobase/plugin-auth-sms": "1.4.0-alpha", - "@nocobase/plugin-backup-restore": "1.4.0-alpha", - "@nocobase/plugin-block-iframe": "1.4.0-alpha", - "@nocobase/plugin-block-workbench": "1.4.0-alpha", - "@nocobase/plugin-calendar": "1.4.0-alpha", - "@nocobase/plugin-charts": "1.4.0-alpha", - "@nocobase/plugin-client": "1.4.0-alpha", - "@nocobase/plugin-collection-sql": "1.4.0-alpha", - "@nocobase/plugin-collection-tree": "1.4.0-alpha", - "@nocobase/plugin-data-source-main": "1.4.0-alpha", - "@nocobase/plugin-data-source-manager": "1.4.0-alpha", - "@nocobase/plugin-data-visualization": "1.4.0-alpha", - "@nocobase/plugin-error-handler": "1.4.0-alpha", - "@nocobase/plugin-field-china-region": "1.4.0-alpha", - "@nocobase/plugin-field-formula": "1.4.0-alpha", - "@nocobase/plugin-field-m2m-array": "1.4.0-alpha", - "@nocobase/plugin-field-markdown-vditor": "1.4.0-alpha", - "@nocobase/plugin-field-sequence": "1.4.0-alpha", - "@nocobase/plugin-field-sort": "1.4.0-alpha", - "@nocobase/plugin-file-manager": "1.4.0-alpha", - "@nocobase/plugin-gantt": "1.4.0-alpha", - "@nocobase/plugin-graph-collection-manager": "1.4.0-alpha", - "@nocobase/plugin-kanban": "1.4.0-alpha", - "@nocobase/plugin-localization": "1.4.0-alpha", - "@nocobase/plugin-logger": "1.4.0-alpha", - "@nocobase/plugin-map": "1.4.0-alpha", - "@nocobase/plugin-mobile": "1.4.0-alpha", - "@nocobase/plugin-mobile-client": "1.4.0-alpha", - "@nocobase/plugin-mock-collections": "1.4.0-alpha", - "@nocobase/plugin-multi-app-manager": "1.4.0-alpha", - "@nocobase/plugin-multi-app-share-collection": "1.4.0-alpha", - "@nocobase/plugin-public-forms": "1.4.0-alpha", - "@nocobase/plugin-snapshot-field": "1.4.0-alpha", - "@nocobase/plugin-system-settings": "1.4.0-alpha", - "@nocobase/plugin-theme-editor": "1.4.0-alpha", - "@nocobase/plugin-ui-schema-storage": "1.4.0-alpha", - "@nocobase/plugin-users": "1.4.0-alpha", - "@nocobase/plugin-user-data-sync": "1.4.0-alpha", - "@nocobase/plugin-verification": "1.4.0-alpha", - "@nocobase/plugin-workflow": "1.4.0-alpha", - "@nocobase/plugin-workflow-action-trigger": "1.4.0-alpha", - "@nocobase/plugin-workflow-aggregate": "1.4.0-alpha", - "@nocobase/plugin-workflow-delay": "1.4.0-alpha", - "@nocobase/plugin-workflow-dynamic-calculation": "1.4.0-alpha", - "@nocobase/plugin-workflow-loop": "1.4.0-alpha", - "@nocobase/plugin-workflow-mailer": "1.4.0-alpha", - "@nocobase/plugin-workflow-manual": "1.4.0-alpha", - "@nocobase/plugin-workflow-parallel": "1.4.0-alpha", - "@nocobase/plugin-workflow-request": "1.4.0-alpha", - "@nocobase/plugin-workflow-sql": "1.4.0-alpha", - "@nocobase/plugin-workflow-notification": "1.4.0-alpha", - "@nocobase/server": "1.4.0-alpha", - "@nocobase/plugin-notification-email": "1.4.0-alpha", - "@nocobase/plugin-notification-manager": "1.4.0-alpha", + "@nocobase/plugin-acl": "1.4.0-alpha.11", + "@nocobase/plugin-action-bulk-edit": "1.4.0-alpha.11", + "@nocobase/plugin-action-bulk-update": "1.4.0-alpha.11", + "@nocobase/plugin-action-custom-request": "1.4.0-alpha.11", + "@nocobase/plugin-action-duplicate": "1.4.0-alpha.11", + "@nocobase/plugin-action-export": "1.4.0-alpha.11", + "@nocobase/plugin-action-import": "1.4.0-alpha.11", + "@nocobase/plugin-action-print": "1.4.0-alpha.11", + "@nocobase/plugin-api-doc": "1.4.0-alpha.11", + "@nocobase/plugin-api-keys": "1.4.0-alpha.11", + "@nocobase/plugin-audit-logs": "1.4.0-alpha.11", + "@nocobase/plugin-auth": "1.4.0-alpha.11", + "@nocobase/plugin-auth-sms": "1.4.0-alpha.11", + "@nocobase/plugin-backup-restore": "1.4.0-alpha.11", + "@nocobase/plugin-block-iframe": "1.4.0-alpha.11", + "@nocobase/plugin-block-workbench": "1.4.0-alpha.11", + "@nocobase/plugin-calendar": "1.4.0-alpha.11", + "@nocobase/plugin-charts": "1.4.0-alpha.11", + "@nocobase/plugin-client": "1.4.0-alpha.11", + "@nocobase/plugin-collection-sql": "1.4.0-alpha.11", + "@nocobase/plugin-collection-tree": "1.4.0-alpha.11", + "@nocobase/plugin-data-source-main": "1.4.0-alpha.11", + "@nocobase/plugin-data-source-manager": "1.4.0-alpha.11", + "@nocobase/plugin-data-visualization": "1.4.0-alpha.11", + "@nocobase/plugin-error-handler": "1.4.0-alpha.11", + "@nocobase/plugin-field-china-region": "1.4.0-alpha.11", + "@nocobase/plugin-field-formula": "1.4.0-alpha.11", + "@nocobase/plugin-field-m2m-array": "1.4.0-alpha.11", + "@nocobase/plugin-field-markdown-vditor": "1.4.0-alpha.11", + "@nocobase/plugin-field-sequence": "1.4.0-alpha.11", + "@nocobase/plugin-field-sort": "1.4.0-alpha.11", + "@nocobase/plugin-file-manager": "1.4.0-alpha.11", + "@nocobase/plugin-gantt": "1.4.0-alpha.11", + "@nocobase/plugin-graph-collection-manager": "1.4.0-alpha.11", + "@nocobase/plugin-kanban": "1.4.0-alpha.11", + "@nocobase/plugin-localization": "1.4.0-alpha.11", + "@nocobase/plugin-logger": "1.4.0-alpha.11", + "@nocobase/plugin-map": "1.4.0-alpha.11", + "@nocobase/plugin-mobile": "1.4.0-alpha.11", + "@nocobase/plugin-mobile-client": "1.4.0-alpha.11", + "@nocobase/plugin-mock-collections": "1.4.0-alpha.11", + "@nocobase/plugin-multi-app-manager": "1.4.0-alpha.11", + "@nocobase/plugin-multi-app-share-collection": "1.4.0-alpha.11", + "@nocobase/plugin-notification-email": "1.4.0-alpha.11", + "@nocobase/plugin-notification-in-app-message": "1.4.0-alpha.11", + "@nocobase/plugin-notification-manager": "1.4.0-alpha.11", + "@nocobase/plugin-public-forms": "1.4.0-alpha.11", + "@nocobase/plugin-snapshot-field": "1.4.0-alpha.11", + "@nocobase/plugin-system-settings": "1.4.0-alpha.11", + "@nocobase/plugin-theme-editor": "1.4.0-alpha.11", + "@nocobase/plugin-ui-schema-storage": "1.4.0-alpha.11", + "@nocobase/plugin-user-data-sync": "1.4.0-alpha.11", + "@nocobase/plugin-users": "1.4.0-alpha.11", + "@nocobase/plugin-verification": "1.4.0-alpha.11", + "@nocobase/plugin-workflow": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-action-trigger": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-aggregate": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-delay": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-dynamic-calculation": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-loop": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-mailer": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-manual": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-notification": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-parallel": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-request": "1.4.0-alpha.11", + "@nocobase/plugin-workflow-sql": "1.4.0-alpha.11", + "@nocobase/server": "1.4.0-alpha.11", "cronstrue": "^2.11.0", "fs-extra": "^11.1.1" }, @@ -109,6 +110,7 @@ "@nocobase/plugin-kanban", "@nocobase/plugin-logger", "@nocobase/plugin-notification-manager", + "@nocobase/plugin-notification-in-app-message", "@nocobase/plugin-mobile", "@nocobase/plugin-system-settings", "@nocobase/plugin-ui-schema-storage", diff --git a/packages/presets/nocobase/src/server/index.ts b/packages/presets/nocobase/src/server/index.ts index fae0c9a7f1..0a1724d41f 100644 --- a/packages/presets/nocobase/src/server/index.ts +++ b/packages/presets/nocobase/src/server/index.ts @@ -7,9 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { Plugin, PluginManager } from '@nocobase/server'; +import { findBuiltInPlugins, findLocalPlugins, packageNameTrim, Plugin, PluginManager } from '@nocobase/server'; import _ from 'lodash'; -import { findBuiltInPlugins, findLocalPlugins, trim } from './findPackageNames'; export class PresetNocoBase extends Plugin { splitNames(name: string) { @@ -43,7 +42,7 @@ export class PresetNocoBase extends Plugin { }); const plugins1 = await findBuiltInPlugins(); const plugins2 = await findLocalPlugins(); - return trim(_.uniq([...plugins1, ...plugins2, ...items.map((item) => item.name)])); + return packageNameTrim(_.uniq([...plugins1, ...plugins2, ...items.map((item) => item.name)])); } async getAllPlugins(locale = 'en-US') { diff --git a/release.sh b/release.sh index 7bdf75a1fe..1cc1994bcd 100755 --- a/release.sh +++ b/release.sh @@ -1,13 +1,31 @@ current_version=$(jq -r '.version' lerna.json) -IFS='.-' read -r major minor patch label <<< "$current_version" +IFS='.-' read -r major minor patch label pre <<< "$current_version" -if [ "$1" == '--is-feat' ]; then +if [[ "$label" == 'beta' || "$2" == '--is-beta' ]]; then + if [ "$1" == '--is-feat' ]; then new_version="$major.$minor.0-beta" echo $new_version; -else + else new_patch=$((patch + 1)) new_version="$major.$minor.$new_patch-$label" echo $new_version; + fi +else + # alpha + if [ "$1" == '--is-feat' ]; then + new_minor=$((minor + 1)) + new_version="$major.$new_minor.0-alpha.0" + echo $new_version; + else + if [ -z "$pre" ]; then + new_version="$major.$minor.$patch-alpha.0" + echo $new_version; + else + new_pre=$((pre + 1)) + new_version="$major.$minor.$patch-alpha.$new_pre" + echo $new_version; + fi + fi fi lerna version $new_version --preid alpha --force-publish=* --no-git-tag-version -y @@ -36,3 +54,4 @@ git add . git commit -m "chore(versions): 😊 publish v$(jq -r '.version' lerna.json)" git tag v$(jq -r '.version' lerna.json) # git push --atomic origin main v$(jq -r '.version' lerna.json) + diff --git a/scripts/release/changelogAndRelease.js b/scripts/release/changelogAndRelease.js index ad39e59f7c..b2c9d639bb 100644 --- a/scripts/release/changelogAndRelease.js +++ b/scripts/release/changelogAndRelease.js @@ -372,7 +372,7 @@ async function getVersion() { if (!from || !to) { // git tag -l --sort=version:refname | grep "v*-ver" | tail -2 const tagPattern = `v*-${ver}`; - const { stdout: tags } = await execa(`git tag -l --sort=version:refname | grep "${tagPattern}" | tail -2`, { + const { stdout: tags } = await execa(`git tag -l --sort=creatordate | grep "${tagPattern}" | tail -2`, { shell: true, }); [from, to] = tags.split('\n'); @@ -381,7 +381,7 @@ async function getVersion() { return { from, to }; } -async function postCMS(title, content, contentCN) { +async function postCMS(tag, content, contentCN) { const { cmsToken, cmsURL } = program.opts(); if (!cmsToken || !cmsURL) { console.error('No cmsToken or cmsURL provided'); @@ -394,13 +394,16 @@ async function postCMS(title, content, contentCN) { Authorization: `Bearer ${cmsToken}`, }, params: { - filterKeys: ['title'], + filterKeys: ['slug'], }, data: { - title, - title_cn: title, + slug: tag, + title: `Nocobase ${tag}`, + title_cn: `Nocobase ${tag}`, content, content_cn: contentCN, + description: `Release Note of ${tag}`, + description_cn: `${tag} 更新日志`, tags: [4], status: 'drafted', author: 'nocobase [bot]', diff --git a/storage/.gitignore b/storage/.gitignore index 62d0d471f9..17d5242569 100644 --- a/storage/.gitignore +++ b/storage/.gitignore @@ -7,3 +7,4 @@ app-upgrading /verdaccio/storage libs scripts +.cache \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8fc7c43c75..f88ec0da2b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -226,23 +226,10 @@ dependencies: "@ctrl/tinycolor" "^3.6.1" -"@ant-design/cssinjs@^1", "@ant-design/cssinjs@^1.11.1", "@ant-design/cssinjs@^1.3.1": - version "1.18.1" - resolved "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.18.1.tgz#ebb0aa28c751ce2203f149554d4a09b2c09174c0" - integrity sha512-1JURAPrsjK1GwpqByTq3bJ7nF7lbMKDZpehqeR2n8/IR5O58/W1U4VcOeaw5ZyTHri3tEMcom7dyP2tvxpW54g== - dependencies: - "@babel/runtime" "^7.11.1" - "@emotion/hash" "^0.8.0" - "@emotion/unitless" "^0.7.5" - classnames "^2.3.1" - csstype "3.1.2" - rc-util "^5.35.0" - stylis "^4.0.13" - -"@ant-design/cssinjs@^1.18.2": - version "1.18.2" - resolved "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.18.2.tgz#d993a64c1d0bf51f4a9d662ddc8ed8426977b5c3" - integrity sha512-514V9rjLaFYb3v4s55/8bg2E6fb81b99s3crDZf4nSwtiDLLXs8axnIph+q2TVkY2hbJPZOn/cVsVcnLkzFy7w== +"@ant-design/cssinjs@^1", "@ant-design/cssinjs@^1.11.1", "@ant-design/cssinjs@^1.18.2", "@ant-design/cssinjs@^1.21.1", "@ant-design/cssinjs@^1.3.1": + version "1.21.1" + resolved "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.21.1.tgz#7320813c5f747e0cde52c388eff5198d78d57230" + integrity sha512-tyWnlK+XH7Bumd0byfbCiZNK43HEubMoCcu9VxwsAwiHdHTgWa+tMN0/yvxa+e8EzuFP1WdUNNPclRpVtD33lg== dependencies: "@babel/runtime" "^7.11.1" "@emotion/hash" "^0.8.0" @@ -250,7 +237,7 @@ classnames "^2.3.1" csstype "^3.1.3" rc-util "^5.35.0" - stylis "^4.0.13" + stylis "^4.3.3" "@ant-design/icons-svg@^4.2.1", "@ant-design/icons-svg@^4.3.0": version "4.3.1" @@ -1621,11 +1608,25 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" +"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": + version "7.26.2" + resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2", "@babel/compat-data@^7.25.4": version "7.25.4" resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== +"@babel/compat-data@^7.25.9", "@babel/compat-data@^7.26.0": + version "7.26.2" + resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" + integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== + "@babel/core@7.23.2": version "7.23.2" resolved "https://registry.npmmirror.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94" @@ -1689,6 +1690,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.21.3": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.23.9": version "7.24.3" resolved "https://registry.npmmirror.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" @@ -1759,6 +1781,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.9", "@babel/generator@^7.26.0": + version "7.26.2" + resolved "https://registry.npmmirror.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" + integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== + dependencies: + "@babel/parser" "^7.26.2" + "@babel/types" "^7.26.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -1773,6 +1806,13 @@ dependencies: "@babel/types" "^7.24.7" +"@babel/helper-annotate-as-pure@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" + integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== + dependencies: + "@babel/types" "^7.25.9" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz#37d66feb012024f2422b762b9b2a7cfe27c7fba3" @@ -1781,6 +1821,14 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz#f41752fe772a578e67286e6779a68a5a92de1ee9" + integrity sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8", "@babel/helper-compilation-targets@^7.25.2": version "7.25.2" resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" @@ -1792,6 +1840,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== + dependencies: + "@babel/compat-data" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.24.7": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz#a109bf9c3d58dfed83aaf42e85633c89f43a6253" @@ -1818,6 +1877,19 @@ "@babel/traverse" "^7.25.4" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" + integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/traverse" "^7.25.9" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6": version "7.22.15" resolved "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" @@ -1836,6 +1908,15 @@ regexpu-core "^5.3.1" semver "^6.3.1" +"@babel/helper-create-regexp-features-plugin@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz#3e8999db94728ad2b2458d7a470e7770b7764e26" + integrity sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + regexpu-core "^6.1.1" + semver "^6.3.1" + "@babel/helper-define-polyfill-provider@^0.6.2": version "0.6.2" resolved "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" @@ -1875,6 +1956,14 @@ "@babel/traverse" "^7.24.8" "@babel/types" "^7.24.8" +"@babel/helper-member-expression-to-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" + integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.5": version "7.22.15" resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" @@ -1890,6 +1979,14 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-module-transforms@^7.23.0", "@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.25.0", "@babel/helper-module-transforms@^7.25.2": version "7.25.2" resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" @@ -1900,6 +1997,15 @@ "@babel/helper-validator-identifier" "^7.24.7" "@babel/traverse" "^7.25.2" +"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-optimise-call-expression@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" @@ -1907,11 +2013,23 @@ dependencies: "@babel/types" "^7.24.7" +"@babel/helper-optimise-call-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" + integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== + dependencies: + "@babel/types" "^7.25.9" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.24.8" resolved "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== +"@babel/helper-plugin-utils@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" + integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== + "@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz#d2f0fbba059a42d68e5e378feaf181ef6055365e" @@ -1921,6 +2039,15 @@ "@babel/helper-wrap-function" "^7.25.0" "@babel/traverse" "^7.25.0" +"@babel/helper-remap-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" + integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-wrap-function" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-replace-supers@^7.24.7", "@babel/helper-replace-supers@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" @@ -1930,6 +2057,15 @@ "@babel/helper-optimise-call-expression" "^7.24.7" "@babel/traverse" "^7.25.0" +"@babel/helper-replace-supers@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz#ba447224798c3da3f8713fc272b145e33da6a5c5" + integrity sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.25.9" + "@babel/helper-optimise-call-expression" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -1945,6 +2081,14 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-simple-access@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz#6d51783299884a2c74618d6ef0f86820ec2e7739" + integrity sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" @@ -1953,6 +2097,14 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" + integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== + dependencies: + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" @@ -1970,6 +2122,11 @@ resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" @@ -1980,11 +2137,21 @@ resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + "@babel/helper-validator-option@^7.24.8": version "7.24.8" resolved "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + "@babel/helper-wrap-function@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz#dab12f0f593d6ca48c0062c28bcfb14ebe812f81" @@ -1994,6 +2161,15 @@ "@babel/traverse" "^7.25.0" "@babel/types" "^7.25.0" +"@babel/helper-wrap-function@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" + integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g== + dependencies: + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helpers@^7.23.2", "@babel/helpers@^7.23.6": version "7.23.6" resolved "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.23.6.tgz#d03af2ee5fb34691eec0cda90f5ecbb4d4da145a" @@ -2020,6 +2196,14 @@ "@babel/template" "^7.25.0" "@babel/types" "^7.25.0" +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" + "@babel/highlight@^7.23.4": version "7.23.4" resolved "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" @@ -2073,6 +2257,13 @@ dependencies: "@babel/types" "^7.25.6" +"@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": + version "7.26.2" + resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" + integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== + dependencies: + "@babel/types" "^7.26.0" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.3": version "7.25.3" resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz#dca427b45a6c0f5c095a1c639dfe2476a3daba7f" @@ -2081,6 +2272,14 @@ "@babel/helper-plugin-utils" "^7.24.8" "@babel/traverse" "^7.25.3" +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" + integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz#cd0c583e01369ef51676bdb3d7b603e17d2b3f73" @@ -2088,6 +2287,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30" + integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz#749bde80356b295390954643de7635e0dffabe73" @@ -2095,6 +2301,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137" + integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz#e4eabdd5109acc399b38d7999b2ef66fc2022f89" @@ -2104,6 +2317,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-transform-optional-chaining" "^7.24.7" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1" + integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz#3a82a70e7cb7294ad2559465ebcb871dfbf078fb" @@ -2112,6 +2334,14 @@ "@babel/helper-plugin-utils" "^7.24.8" "@babel/traverse" "^7.25.0" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e" + integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-proposal-export-namespace-from@^7.14.5": version "7.18.9" resolved "https://registry.npmmirror.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" @@ -2174,6 +2404,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-syntax-import-assertions@^7.26.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f" + integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-import-attributes@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" @@ -2181,6 +2418,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-syntax-import-attributes@^7.26.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -2202,6 +2446,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -2258,6 +2509,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -2273,6 +2531,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-arrow-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" + integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-async-generator-functions@^7.25.4": version "7.25.4" resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz#2afd4e639e2d055776c9f091b6c0c180ed8cf083" @@ -2283,6 +2548,15 @@ "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/traverse" "^7.25.4" +"@babel/plugin-transform-async-generator-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz#1b18530b077d18a407c494eb3d1d72da505283a2" + integrity sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-remap-async-to-generator" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-transform-async-to-generator@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz#72a3af6c451d575842a7e9b5a02863414355bdcc" @@ -2292,6 +2566,15 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-remap-async-to-generator" "^7.24.7" +"@babel/plugin-transform-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71" + integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-remap-async-to-generator" "^7.25.9" + "@babel/plugin-transform-block-scoped-functions@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz#a4251d98ea0c0f399dafe1a35801eaba455bbf1f" @@ -2299,6 +2582,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-block-scoped-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz#5700691dbd7abb93de300ca7be94203764fce458" + integrity sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-block-scoping@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz#23a6ed92e6b006d26b1869b1c91d1b917c2ea2ac" @@ -2306,6 +2596,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-block-scoping@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1" + integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-class-properties@^7.25.4": version "7.25.4" resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz#bae7dbfcdcc2e8667355cd1fb5eda298f05189fd" @@ -2314,6 +2611,14 @@ "@babel/helper-create-class-features-plugin" "^7.25.4" "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-class-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f" + integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-class-static-block@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" @@ -2323,6 +2628,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" +"@babel/plugin-transform-class-static-block@^7.26.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0" + integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-classes@^7.25.4": version "7.25.4" resolved "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.4.tgz#d29dbb6a72d79f359952ad0b66d88518d65ef89a" @@ -2335,6 +2648,18 @@ "@babel/traverse" "^7.25.4" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52" + integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/traverse" "^7.25.9" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz#4cab3214e80bc71fae3853238d13d097b004c707" @@ -2343,6 +2668,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/template" "^7.24.7" +"@babel/plugin-transform-computed-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b" + integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/plugin-transform-destructuring@^7.24.8": version "7.24.8" resolved "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz#c828e814dbe42a2718a838c2a2e16a408e055550" @@ -2350,6 +2683,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-destructuring@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1" + integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-dotall-regex@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz#5f8bf8a680f2116a7207e16288a5f974ad47a7a0" @@ -2358,6 +2698,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-dotall-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a" + integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-duplicate-keys@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz#dd20102897c9a2324e5adfffb67ff3610359a8ee" @@ -2365,6 +2713,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-duplicate-keys@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d" + integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz#809af7e3339466b49c034c683964ee8afb3e2604" @@ -2373,6 +2728,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.25.0" "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31" + integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-dynamic-import@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" @@ -2381,6 +2744,13 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" +"@babel/plugin-transform-dynamic-import@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8" + integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-exponentiation-operator@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz#b629ee22645f412024297d5245bce425c31f9b0d" @@ -2389,6 +2759,14 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-exponentiation-operator@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz#ece47b70d236c1d99c263a1e22b62dc20a4c8b0f" + integrity sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-export-namespace-from@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz#176d52d8d8ed516aeae7013ee9556d540c53f197" @@ -2397,6 +2775,13 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" +"@babel/plugin-transform-export-namespace-from@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2" + integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-for-of@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz#f25b33f72df1d8be76399e1b8f3f9d366eb5bc70" @@ -2405,6 +2790,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" +"@babel/plugin-transform-for-of@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz#4bdc7d42a213397905d89f02350c5267866d5755" + integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-function-name@^7.25.1": version "7.25.1" resolved "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz#b85e773097526c1a4fc4ba27322748643f26fc37" @@ -2414,6 +2807,15 @@ "@babel/helper-plugin-utils" "^7.24.8" "@babel/traverse" "^7.25.1" +"@babel/plugin-transform-function-name@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97" + integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-transform-json-strings@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz#f3e9c37c0a373fee86e36880d45b3664cedaf73a" @@ -2422,6 +2824,13 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-json-strings" "^7.8.3" +"@babel/plugin-transform-json-strings@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660" + integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-literals@^7.25.2": version "7.25.2" resolved "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz#deb1ad14fc5490b9a65ed830e025bca849d8b5f3" @@ -2429,6 +2838,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de" + integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-logical-assignment-operators@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz#a58fb6eda16c9dc8f9ff1c7b1ba6deb7f4694cb0" @@ -2437,6 +2853,13 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" +"@babel/plugin-transform-logical-assignment-operators@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7" + integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-member-expression-literals@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz#3b4454fb0e302e18ba4945ba3246acb1248315df" @@ -2444,6 +2867,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-member-expression-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de" + integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-modules-amd@7.24.7", "@babel/plugin-transform-modules-amd@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz#65090ed493c4a834976a3ca1cde776e6ccff32d7" @@ -2452,6 +2882,14 @@ "@babel/helper-module-transforms" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-modules-amd@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5" + integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-modules-commonjs@7.23.0": version "7.23.0" resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz#b3dba4757133b2762c00f4f94590cf6d52602481" @@ -2470,6 +2908,15 @@ "@babel/helper-plugin-utils" "^7.24.8" "@babel/helper-simple-access" "^7.24.7" +"@babel/plugin-transform-modules-commonjs@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz#d165c8c569a080baf5467bda88df6425fc060686" + integrity sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-simple-access" "^7.25.9" + "@babel/plugin-transform-modules-systemjs@^7.25.0": version "7.25.0" resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz#8f46cdc5f9e5af74f3bd019485a6cbe59685ea33" @@ -2480,6 +2927,16 @@ "@babel/helper-validator-identifier" "^7.24.7" "@babel/traverse" "^7.25.0" +"@babel/plugin-transform-modules-systemjs@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8" + integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-transform-modules-umd@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz#edd9f43ec549099620df7df24e7ba13b5c76efc8" @@ -2488,6 +2945,14 @@ "@babel/helper-module-transforms" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-modules-umd@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9" + integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz#9042e9b856bc6b3688c0c2e4060e9e10b1460923" @@ -2496,6 +2961,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a" + integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-new-target@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz#31ff54c4e0555cc549d5816e4ab39241dfb6ab00" @@ -2503,6 +2976,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-new-target@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd" + integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz#1de4534c590af9596f53d67f52a92f12db984120" @@ -2511,6 +2991,13 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" +"@babel/plugin-transform-nullish-coalescing-operator@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz#bcb1b0d9e948168102d5f7104375ca21c3266949" + integrity sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-numeric-separator@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz#bea62b538c80605d8a0fac9b40f48e97efa7de63" @@ -2519,6 +3006,13 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-numeric-separator" "^7.10.4" +"@babel/plugin-transform-numeric-separator@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1" + integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-object-rest-spread@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz#d13a2b93435aeb8a197e115221cab266ba6e55d6" @@ -2529,6 +3023,15 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.24.7" +"@babel/plugin-transform-object-rest-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18" + integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + "@babel/plugin-transform-object-super@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz#66eeaff7830bba945dd8989b632a40c04ed625be" @@ -2537,6 +3040,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-replace-supers" "^7.24.7" +"@babel/plugin-transform-object-super@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03" + integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/plugin-transform-optional-catch-binding@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz#00eabd883d0dd6a60c1c557548785919b6e717b4" @@ -2545,6 +3056,13 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" +"@babel/plugin-transform-optional-catch-binding@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3" + integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8": version "7.24.8" resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz#bb02a67b60ff0406085c13d104c99a835cdf365d" @@ -2554,6 +3072,14 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +"@babel/plugin-transform-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" + integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-parameters@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz#5881f0ae21018400e320fc7eb817e529d1254b68" @@ -2561,6 +3087,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-parameters@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257" + integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-private-methods@^7.25.4": version "7.25.4" resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.4.tgz#9bbefbe3649f470d681997e0b64a4b254d877242" @@ -2569,6 +3102,14 @@ "@babel/helper-create-class-features-plugin" "^7.25.4" "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-private-methods@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" + integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-private-property-in-object@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz#4eec6bc701288c1fab5f72e6a4bbc9d67faca061" @@ -2579,6 +3120,15 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@babel/plugin-transform-private-property-in-object@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33" + integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-property-literals@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz#f0d2ed8380dfbed949c42d4d790266525d63bbdc" @@ -2586,6 +3136,34 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-property-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f" + integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-constant-elements@^7.21.3": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz#08a1de35a301929b60fdf2788a54b46cd8ecd0ef" + integrity sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-display-name@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz#4b79746b59efa1f38c8695065a92a9f5afb24f7d" + integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-react-jsx-development@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz#8fd220a77dd139c07e25225a903b8be8c829e0d7" + integrity sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/plugin-transform-react-jsx-self@^7.21.0", "@babel/plugin-transform-react-jsx-self@^7.23.3": version "7.23.3" resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz#ed3e7dadde046cce761a8e3cf003a13d1a7972d9" @@ -2600,6 +3178,25 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-react-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166" + integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/plugin-transform-react-pure-annotations@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz#ea1c11b2f9dbb8e2d97025f43a3b5bc47e18ae62" + integrity sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-regenerator@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz#021562de4534d8b4b1851759fd7af4e05d2c47f8" @@ -2608,6 +3205,22 @@ "@babel/helper-plugin-utils" "^7.24.7" regenerator-transform "^0.15.2" +"@babel/plugin-transform-regenerator@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" + integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-regexp-modifiers@^7.26.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850" + integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-reserved-words@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz#80037fe4fbf031fc1125022178ff3938bb3743a4" @@ -2615,6 +3228,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-reserved-words@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce" + integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-shorthand-properties@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz#85448c6b996e122fa9e289746140aaa99da64e73" @@ -2622,6 +3242,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-shorthand-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" + integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-spread@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz#e8a38c0fde7882e0fb8f160378f74bd885cc7bb3" @@ -2630,6 +3257,14 @@ "@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" +"@babel/plugin-transform-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9" + integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-sticky-regex@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz#96ae80d7a7e5251f657b5cf18f1ea6bf926f5feb" @@ -2637,6 +3272,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-sticky-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32" + integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-template-literals@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz#a05debb4a9072ae8f985bcf77f3f215434c8f8c8" @@ -2644,6 +3286,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-template-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz#6dbd4a24e8fad024df76d1fac6a03cf413f60fe1" + integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-typeof-symbol@^7.24.8": version "7.24.8" resolved "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz#383dab37fb073f5bfe6e60c654caac309f92ba1c" @@ -2651,6 +3300,24 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-typeof-symbol@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz#224ba48a92869ddbf81f9b4a5f1204bbf5a2bc4b" + integrity sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz#69267905c2b33c2ac6d8fe765e9dc2ddc9df3849" + integrity sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-syntax-typescript" "^7.25.9" + "@babel/plugin-transform-unicode-escapes@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz#2023a82ced1fb4971630a2e079764502c4148e0e" @@ -2658,6 +3325,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-unicode-escapes@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82" + integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-unicode-property-regex@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz#9073a4cd13b86ea71c3264659590ac086605bbcd" @@ -2666,6 +3340,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-unicode-property-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3" + integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-unicode-regex@^7.24.7": version "7.24.7" resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz#dfc3d4a51127108099b19817c0963be6a2adf19f" @@ -2674,6 +3356,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.7" +"@babel/plugin-transform-unicode-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1" + integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-unicode-sets-regex@^7.25.4": version "7.25.4" resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz#be664c2a0697ffacd3423595d5edef6049e8946c" @@ -2682,6 +3372,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.25.2" "@babel/helper-plugin-utils" "^7.24.8" +"@babel/plugin-transform-unicode-sets-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe" + integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/preset-env@7.25.4": version "7.25.4" resolved "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.25.4.tgz#be23043d43a34a2721cd0f676c7ba6f1481f6af6" @@ -2771,6 +3469,81 @@ core-js-compat "^3.37.1" semver "^6.3.1" +"@babel/preset-env@^7.20.2": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.26.0.tgz#30e5c6bc1bcc54865bff0c5a30f6d4ccdc7fa8b1" + integrity sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw== + dependencies: + "@babel/compat-data" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions" "^7.26.0" + "@babel/plugin-syntax-import-attributes" "^7.26.0" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.25.9" + "@babel/plugin-transform-async-generator-functions" "^7.25.9" + "@babel/plugin-transform-async-to-generator" "^7.25.9" + "@babel/plugin-transform-block-scoped-functions" "^7.25.9" + "@babel/plugin-transform-block-scoping" "^7.25.9" + "@babel/plugin-transform-class-properties" "^7.25.9" + "@babel/plugin-transform-class-static-block" "^7.26.0" + "@babel/plugin-transform-classes" "^7.25.9" + "@babel/plugin-transform-computed-properties" "^7.25.9" + "@babel/plugin-transform-destructuring" "^7.25.9" + "@babel/plugin-transform-dotall-regex" "^7.25.9" + "@babel/plugin-transform-duplicate-keys" "^7.25.9" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-dynamic-import" "^7.25.9" + "@babel/plugin-transform-exponentiation-operator" "^7.25.9" + "@babel/plugin-transform-export-namespace-from" "^7.25.9" + "@babel/plugin-transform-for-of" "^7.25.9" + "@babel/plugin-transform-function-name" "^7.25.9" + "@babel/plugin-transform-json-strings" "^7.25.9" + "@babel/plugin-transform-literals" "^7.25.9" + "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" + "@babel/plugin-transform-member-expression-literals" "^7.25.9" + "@babel/plugin-transform-modules-amd" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.25.9" + "@babel/plugin-transform-modules-systemjs" "^7.25.9" + "@babel/plugin-transform-modules-umd" "^7.25.9" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-new-target" "^7.25.9" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.25.9" + "@babel/plugin-transform-numeric-separator" "^7.25.9" + "@babel/plugin-transform-object-rest-spread" "^7.25.9" + "@babel/plugin-transform-object-super" "^7.25.9" + "@babel/plugin-transform-optional-catch-binding" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + "@babel/plugin-transform-private-methods" "^7.25.9" + "@babel/plugin-transform-private-property-in-object" "^7.25.9" + "@babel/plugin-transform-property-literals" "^7.25.9" + "@babel/plugin-transform-regenerator" "^7.25.9" + "@babel/plugin-transform-regexp-modifiers" "^7.26.0" + "@babel/plugin-transform-reserved-words" "^7.25.9" + "@babel/plugin-transform-shorthand-properties" "^7.25.9" + "@babel/plugin-transform-spread" "^7.25.9" + "@babel/plugin-transform-sticky-regex" "^7.25.9" + "@babel/plugin-transform-template-literals" "^7.25.9" + "@babel/plugin-transform-typeof-symbol" "^7.25.9" + "@babel/plugin-transform-unicode-escapes" "^7.25.9" + "@babel/plugin-transform-unicode-property-regex" "^7.25.9" + "@babel/plugin-transform-unicode-regex" "^7.25.9" + "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.6" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.38.1" + semver "^6.3.1" + "@babel/preset-modules@0.1.6-no-external-plugins": version "0.1.6-no-external-plugins" resolved "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" @@ -2780,6 +3553,29 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/preset-react@^7.18.6": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/preset-react/-/preset-react-7.25.9.tgz#5f473035dc2094bcfdbc7392d0766bd42dce173e" + integrity sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-transform-react-display-name" "^7.25.9" + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/plugin-transform-react-jsx-development" "^7.25.9" + "@babel/plugin-transform-react-pure-annotations" "^7.25.9" + +"@babel/preset-typescript@^7.21.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" + integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.25.9" + "@babel/plugin-transform-typescript" "^7.25.9" + "@babel/regjsgen@^0.8.0": version "0.8.0" resolved "https://registry.npmmirror.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" @@ -2792,10 +3588,10 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.3", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.23.7" - resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.7.tgz#dd7c88deeb218a0f8bd34d5db1aa242e0f203193" - integrity sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA== +"@babel/runtime@^7", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.6", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.3", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.24.1", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== dependencies: regenerator-runtime "^0.14.0" @@ -2826,6 +3622,15 @@ "@babel/parser" "^7.25.0" "@babel/types" "^7.25.0" +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/traverse@^7.23.2", "@babel/traverse@^7.23.6", "@babel/traverse@^7.4.5": version "7.23.6" resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" @@ -2884,6 +3689,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.25.9": + version "7.25.9" + resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/types" "^7.25.9" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.23.6" resolved "https://registry.npmmirror.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" @@ -2893,6 +3711,14 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.21.3", "@babel/types@^7.25.9", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.npmmirror.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/types@^7.24.0": version "7.24.0" resolved "https://registry.npmmirror.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" @@ -2940,10 +3766,10 @@ resolved "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz#923ca57e173c6b232bbbb07347b1be982f03e783" integrity sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A== -"@budibase/handlebars-helpers@^0.13.2": - version "0.13.2" - resolved "https://registry.npmmirror.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.13.2.tgz#73ab51c464e91fd955b429017648e0257060db77" - integrity sha512-/IGqyDcjN9AhKSagCsqQRavpraQRjGeSZeMb62yJiK0b/9r47BjzcOeuQbfRhwK1NlL2cSExNcrkWSxisbPvJQ== +"@budibase/handlebars-helpers@^0.14.0": + version "0.14.0" + resolved "https://registry.npmmirror.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.14.0.tgz#6124bc3d2a84840a03575b7853f7e2df95208da7" + integrity sha512-iuK0HmxqAQ4jjcI46ILE6j1g8FK2wZXVrVFClDByGpiCrQ+TZ2rGZ/UNA2kG3qleZvoI2Izk/gXERmyfREke5w== dependencies: get-object "^0.2.0" get-value "^3.0.1" @@ -3144,6 +3970,52 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@csstools/cascade-layer-name-parser@^1.0.13": + version "1.0.13" + resolved "https://registry.npmmirror.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.13.tgz#6900157489bc53da1f6a66eaccd432025f6cd6fb" + integrity sha512-MX0yLTwtZzr82sQ0zOjqimpZbzjMaK/h2pmlrLK7DCzlmiZLYFpoO94WmN1akRVo6ll/TdpHb53vihHLUMyvng== + +"@csstools/color-helpers@^4.2.1": + version "4.2.1" + resolved "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-4.2.1.tgz#da573554220ccb59757f12de62bf70c6b15645d4" + integrity sha512-CEypeeykO9AN7JWkr1OEOQb0HRzZlPWGwV0Ya6DuVgFdDi6g3ma/cPZ5ZPZM4AWQikDpq/0llnGGlIL+j8afzw== + +"@csstools/css-calc@^1.2.4": + version "1.2.4" + resolved "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-1.2.4.tgz#9d9fb0dca33666cf97659f8f2c343ed0210e0e73" + integrity sha512-tfOuvUQeo7Hz+FcuOd3LfXVp+342pnWUJ7D2y8NUpu1Ww6xnTbHLpz018/y6rtbHifJ3iIEf9ttxXd8KG7nL0Q== + +"@csstools/css-color-parser@^2.0.4": + version "2.0.5" + resolved "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-2.0.5.tgz#ce1fe52f23f35f37bea2cf61ac865115aa17880a" + integrity sha512-lRZSmtl+DSjok3u9hTWpmkxFZnz7stkbZxzKc08aDUsdrWwhSgWo8yq9rq9DaFUtbAyAq2xnH92fj01S+pwIww== + dependencies: + "@csstools/color-helpers" "^4.2.1" + "@csstools/css-calc" "^1.2.4" + +"@csstools/css-parser-algorithms@^2.7.1": + version "2.7.1" + resolved "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz#6d93a8f7d8aeb7cd9ed0868f946e46f021b6aa70" + integrity sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw== + +"@csstools/css-tokenizer@^2.4.1": + version "2.4.1" + resolved "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz#1d8b2e200197cf5f35ceb07ca2dade31f3a00ae8" + integrity sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg== + +"@csstools/media-query-list-parser@^2.1.13": + version "2.1.13" + resolved "https://registry.npmmirror.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz#f00be93f6bede07c14ddf51a168ad2748e4fe9e5" + integrity sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA== + +"@csstools/postcss-cascade-layers@^4.0.6": + version "4.0.6" + resolved "https://registry.npmmirror.com/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-4.0.6.tgz#5a421cd2d5792d1eb8c28e682dc5f2c3b85cb045" + integrity sha512-Xt00qGAQyqAODFiFEJNkTpSUz5VfYqnDLECdlA/Vv17nl/OIV5QfTRHGAXrBGG5YcJyHpJ+GF9gF/RZvOQz4oA== + dependencies: + "@csstools/selector-specificity" "^3.1.1" + postcss-selector-parser "^6.0.13" + "@csstools/postcss-color-function@^1.1.0": version "1.1.1" resolved "https://registry.npmmirror.com/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz#2bd36ab34f82d0497cfacdc9b18d34b5e6f64b6b" @@ -3152,6 +4024,47 @@ "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" +"@csstools/postcss-color-function@^3.0.19": + version "3.0.19" + resolved "https://registry.npmmirror.com/@csstools/postcss-color-function/-/postcss-color-function-3.0.19.tgz#8db83be25bb590a29549b0305bdaa74e76366c62" + integrity sha512-d1OHEXyYGe21G3q88LezWWx31ImEDdmINNDy0LyLNN9ChgN2bPxoubUPiHf9KmwypBMaHmNcMuA/WZOKdZk/Lg== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + +"@csstools/postcss-color-mix-function@^2.0.19": + version "2.0.19" + resolved "https://registry.npmmirror.com/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-2.0.19.tgz#dd5c8cccd95613d11d8a8f96a57c148daa0e6306" + integrity sha512-mLvQlMX+keRYr16AuvuV8WYKUwF+D0DiCqlBdvhQ0KYEtcQl9/is9Ssg7RcIys8x0jIn2h1zstS4izckdZj9wg== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + +"@csstools/postcss-content-alt-text@^1.0.0": + version "1.0.0" + resolved "https://registry.npmmirror.com/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-1.0.0.tgz#f69f74cd7ff679a912a444a274f67b9e0ce67127" + integrity sha512-SkHdj7EMM/57GVvSxSELpUg7zb5eAndBeuvGwFzYtU06/QXJ/h9fuK7wO5suteJzGhm3GDF/EWPCdWV2h1IGHQ== + dependencies: + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + +"@csstools/postcss-exponential-functions@^1.0.9": + version "1.0.9" + resolved "https://registry.npmmirror.com/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-1.0.9.tgz#443b42c26c65b57a84a21d81075dacd93eeb7fd8" + integrity sha512-x1Avr15mMeuX7Z5RJUl7DmjhUtg+Amn5DZRD0fQ2TlTFTcJS8U1oxXQ9e5mA62S2RJgUU6db20CRoJyDvae2EQ== + dependencies: + "@csstools/css-calc" "^1.2.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-font-format-keywords@^1.0.0": version "1.0.1" resolved "https://registry.npmmirror.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz#677b34e9e88ae997a67283311657973150e8b16a" @@ -3159,6 +4072,34 @@ dependencies: postcss-value-parser "^4.2.0" +"@csstools/postcss-font-format-keywords@^3.0.2": + version "3.0.2" + resolved "https://registry.npmmirror.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-3.0.2.tgz#b504cfc60588ac39fa5d1c67ef3da802b1bd7701" + integrity sha512-E0xz2sjm4AMCkXLCFvI/lyl4XO6aN1NCSMMVEOngFDJ+k2rDwfr6NDjWljk1li42jiLNChVX+YFnmfGCigZKXw== + dependencies: + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-gamut-mapping@^1.0.11": + version "1.0.11" + resolved "https://registry.npmmirror.com/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-1.0.11.tgz#7f5b0457fc16df8e0f9dd2fbe86b7e5a0240592c" + integrity sha512-KrHGsUPXRYxboXmJ9wiU/RzDM7y/5uIefLWKFSc36Pok7fxiPyvkSHO51kh+RLZS1W5hbqw9qaa6+tKpTSxa5g== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + +"@csstools/postcss-gradients-interpolation-method@^4.0.20": + version "4.0.20" + resolved "https://registry.npmmirror.com/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-4.0.20.tgz#e2a165719798cd8b503865297d8095c857eba77f" + integrity sha512-ZFl2JBHano6R20KB5ZrB8KdPM2pVK0u+/3cGQ2T8VubJq982I2LSOvQ4/VtxkAXjkPkk1rXt4AD1ni7UjTZ1Og== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + "@csstools/postcss-hwb-function@^1.0.0": version "1.0.2" resolved "https://registry.npmmirror.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz#ab54a9fce0ac102c754854769962f2422ae8aa8b" @@ -3166,6 +4107,17 @@ dependencies: postcss-value-parser "^4.2.0" +"@csstools/postcss-hwb-function@^3.0.18": + version "3.0.18" + resolved "https://registry.npmmirror.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-3.0.18.tgz#267dc59c97033b1108e377c98c45c35b713ea66b" + integrity sha512-3ifnLltR5C7zrJ+g18caxkvSRnu9jBBXCYgnBznRjxm6gQJGnnCO9H6toHfywNdNr/qkiVf2dymERPQLDnjLRQ== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + "@csstools/postcss-ic-unit@^1.0.0": version "1.0.1" resolved "https://registry.npmmirror.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz#28237d812a124d1a16a5acc5c3832b040b303e58" @@ -3174,6 +4126,20 @@ "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" +"@csstools/postcss-ic-unit@^3.0.7": + version "3.0.7" + resolved "https://registry.npmmirror.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-3.0.7.tgz#2a4428c0d19bd456b4bfd60dcbe9e7c4974dfcef" + integrity sha512-YoaNHH2wNZD+c+rHV02l4xQuDpfR8MaL7hD45iJyr+USwvr0LOheeytJ6rq8FN6hXBmEeoJBeXXgGmM8fkhH4g== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-initial@^1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@csstools/postcss-initial/-/postcss-initial-1.0.1.tgz#5aa378de9bfd0e6e377433f8986bdecf579e1268" + integrity sha512-wtb+IbUIrIf8CrN6MLQuFR7nlU5C7PwuebfeEXfjthUha1+XZj2RVi+5k/lukToA24sZkYAiSJfHM8uG/UZIdg== + "@csstools/postcss-is-pseudo-class@^2.0.2": version "2.0.7" resolved "https://registry.npmmirror.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz#846ae6c0d5a1eaa878fce352c544f9c295509cd1" @@ -3182,6 +4148,81 @@ "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" +"@csstools/postcss-is-pseudo-class@^4.0.8": + version "4.0.8" + resolved "https://registry.npmmirror.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-4.0.8.tgz#d2bcc6c2d86d9653c333926a9ea488c2fc221a7f" + integrity sha512-0aj591yGlq5Qac+plaWCbn5cpjs5Sh0daovYUKJUOMjIp70prGH/XPLp7QjxtbFXz3CTvb0H9a35dpEuIuUi3Q== + dependencies: + "@csstools/selector-specificity" "^3.1.1" + postcss-selector-parser "^6.0.13" + +"@csstools/postcss-light-dark-function@^1.0.8": + version "1.0.8" + resolved "https://registry.npmmirror.com/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-1.0.8.tgz#4d4cdad50a9b54b6b3a79cf32bf1cd956e82b0d7" + integrity sha512-x0UtpCyVnERsplUeoaY6nEtp1HxTf4lJjoK/ULEm40DraqFfUdUSt76yoOyX5rGY6eeOUOkurHyYlFHVKv/pew== + dependencies: + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + +"@csstools/postcss-logical-float-and-clear@^2.0.1": + version "2.0.1" + resolved "https://registry.npmmirror.com/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-2.0.1.tgz#c70ed8293cc376b1572bf56794219f54dc58c54d" + integrity sha512-SsrWUNaXKr+e/Uo4R/uIsqJYt3DaggIh/jyZdhy/q8fECoJSKsSMr7nObSLdvoULB69Zb6Bs+sefEIoMG/YfOA== + +"@csstools/postcss-logical-overflow@^1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-1.0.1.tgz#d14631369f43ef989c7e32f051ddb6952a8ce35c" + integrity sha512-Kl4lAbMg0iyztEzDhZuQw8Sj9r2uqFDcU1IPl+AAt2nue8K/f1i7ElvKtXkjhIAmKiy5h2EY8Gt/Cqg0pYFDCw== + +"@csstools/postcss-logical-overscroll-behavior@^1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-1.0.1.tgz#9305a6f0d08bb7b5f1a228272951f72d3bf9d44f" + integrity sha512-+kHamNxAnX8ojPCtV8WPcUP3XcqMFBSDuBuvT6MHgq7oX4IQxLIXKx64t7g9LiuJzE7vd06Q9qUYR6bh4YnGpQ== + +"@csstools/postcss-logical-resize@^2.0.1": + version "2.0.1" + resolved "https://registry.npmmirror.com/@csstools/postcss-logical-resize/-/postcss-logical-resize-2.0.1.tgz#a46c1b51055db96fb63af3bfe58909c773aea377" + integrity sha512-W5Gtwz7oIuFcKa5SmBjQ2uxr8ZoL7M2bkoIf0T1WeNqljMkBrfw1DDA8/J83k57NQ1kcweJEjkJ04pUkmyee3A== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-logical-viewport-units@^2.0.11": + version "2.0.11" + resolved "https://registry.npmmirror.com/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-2.0.11.tgz#f87fcaecd33403e19cb4d77a19e62ede8ed4ec13" + integrity sha512-ElITMOGcjQtvouxjd90WmJRIw1J7KMP+M+O87HaVtlgOOlDt1uEPeTeii8qKGe2AiedEp0XOGIo9lidbiU2Ogg== + dependencies: + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/utilities" "^1.0.0" + +"@csstools/postcss-media-minmax@^1.1.8": + version "1.1.8" + resolved "https://registry.npmmirror.com/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.1.8.tgz#a90b576805312b1bea7bda7d1726402b7f5ef430" + integrity sha512-KYQCal2i7XPNtHAUxCECdrC7tuxIWQCW+s8eMYs5r5PaAiVTeKwlrkRS096PFgojdNCmHeG0Cb7njtuNswNf+w== + dependencies: + "@csstools/css-calc" "^1.2.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/media-query-list-parser" "^2.1.13" + +"@csstools/postcss-media-queries-aspect-ratio-number-values@^2.0.11": + version "2.0.11" + resolved "https://registry.npmmirror.com/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.11.tgz#bb93203839521e99101b6adbab72dc9d9b57c9bc" + integrity sha512-YD6jrib20GRGQcnOu49VJjoAnQ/4249liuz7vTpy/JfgqQ1Dlc5eD4HPUMNLOw9CWey9E6Etxwf/xc/ZF8fECA== + dependencies: + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/media-query-list-parser" "^2.1.13" + +"@csstools/postcss-nested-calc@^3.0.2": + version "3.0.2" + resolved "https://registry.npmmirror.com/@csstools/postcss-nested-calc/-/postcss-nested-calc-3.0.2.tgz#72ae4d087987ab5596397f5c2e5db4403b81c4a9" + integrity sha512-ySUmPyawiHSmBW/VI44+IObcKH0v88LqFe0d09Sb3w4B1qjkaROc6d5IA3ll9kjD46IIX/dbO5bwFN/swyoyZA== + dependencies: + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + "@csstools/postcss-normalize-display-values@^1.0.0": version "1.0.1" resolved "https://registry.npmmirror.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz#15da54a36e867b3ac5163ee12c1d7f82d4d612c3" @@ -3189,6 +4230,13 @@ dependencies: postcss-value-parser "^4.2.0" +"@csstools/postcss-normalize-display-values@^3.0.2": + version "3.0.2" + resolved "https://registry.npmmirror.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-3.0.2.tgz#9013e6ade2fbd4cd725438c9ff0b1000062cf20d" + integrity sha512-fCapyyT/dUdyPtrelQSIV+d5HqtTgnNP/BEG9IuhgXHt93Wc4CfC1bQ55GzKAjWrZbgakMQ7MLfCXEf3rlZJOw== + dependencies: + postcss-value-parser "^4.2.0" + "@csstools/postcss-oklab-function@^1.1.0": version "1.1.1" resolved "https://registry.npmmirror.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz#88cee0fbc8d6df27079ebd2fa016ee261eecf844" @@ -3197,6 +4245,17 @@ "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" +"@csstools/postcss-oklab-function@^3.0.19": + version "3.0.19" + resolved "https://registry.npmmirror.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-3.0.19.tgz#3bd0719914780fb53558af11958d0f4e6d2f952e" + integrity sha512-e3JxXmxjU3jpU7TzZrsNqSX4OHByRC3XjItV3Ieo/JEQmLg5rdOL4lkv/1vp27gXemzfNt44F42k/pn0FpE21Q== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + "@csstools/postcss-progressive-custom-properties@^1.1.0", "@csstools/postcss-progressive-custom-properties@^1.3.0": version "1.3.0" resolved "https://registry.npmmirror.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz#542292558384361776b45c85226b9a3a34f276fa" @@ -3204,6 +4263,31 @@ dependencies: postcss-value-parser "^4.2.0" +"@csstools/postcss-progressive-custom-properties@^3.3.0": + version "3.3.0" + resolved "https://registry.npmmirror.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.3.0.tgz#20177d3fc61d8f170c4ee1686f3d2ab6eec27bbb" + integrity sha512-W2oV01phnILaRGYPmGFlL2MT/OgYjQDrL9sFlbdikMFi6oQkFki9B86XqEWR7HCsTZFVq7dbzr/o71B75TKkGg== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-relative-color-syntax@^2.0.19": + version "2.0.19" + resolved "https://registry.npmmirror.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.19.tgz#246b3a782e88df58184943c2471209c3d2085d65" + integrity sha512-MxUMSNvio1WwuS6WRLlQuv6nNPXwIWUFzBBAvL/tBdWfiKjiJnAa6eSSN5gtaacSqUkQ/Ce5Z1OzLRfeaWhADA== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + +"@csstools/postcss-scope-pseudo-class@^3.0.1": + version "3.0.1" + resolved "https://registry.npmmirror.com/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-3.0.1.tgz#c5454ea2fb3cf9beaf212d3a631a5c18cd4fbc14" + integrity sha512-3ZFonK2gfgqg29gUJ2w7xVw2wFJ1eNWVDONjbzGkm73gJHVCYK5fnCqlLr+N+KbEfv2XbWAO0AaOJCFB6Fer6A== + dependencies: + postcss-selector-parser "^6.0.13" + "@csstools/postcss-stepped-value-functions@^1.0.0": version "1.0.1" resolved "https://registry.npmmirror.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz#f8772c3681cc2befed695e2b0b1d68e22f08c4f4" @@ -3211,16 +4295,62 @@ dependencies: postcss-value-parser "^4.2.0" +"@csstools/postcss-stepped-value-functions@^3.0.10": + version "3.0.10" + resolved "https://registry.npmmirror.com/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-3.0.10.tgz#41cf7b2fc6abc9216b453137a35aeeeb056d70d9" + integrity sha512-MZwo0D0TYrQhT5FQzMqfy/nGZ28D1iFtpN7Su1ck5BPHS95+/Y5O9S4kEvo76f2YOsqwYcT8ZGehSI1TnzuX2g== + dependencies: + "@csstools/css-calc" "^1.2.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + +"@csstools/postcss-text-decoration-shorthand@^3.0.7": + version "3.0.7" + resolved "https://registry.npmmirror.com/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-3.0.7.tgz#58dc60bb0718f6ec7d0a41d4124cf45a6813aeaa" + integrity sha512-+cptcsM5r45jntU6VjotnkC9GteFR7BQBfZ5oW7inLCxj7AfLGAzMbZ60hKTP13AULVZBdxky0P8um0IBfLHVA== + dependencies: + "@csstools/color-helpers" "^4.2.1" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-trigonometric-functions@^3.0.10": + version "3.0.10" + resolved "https://registry.npmmirror.com/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-3.0.10.tgz#0ad99b0a2a77cdd9c957b6e6e83221acf9b6afd8" + integrity sha512-G9G8moTc2wiad61nY5HfvxLiM/myX0aYK4s1x8MQlPH29WDPxHQM7ghGgvv2qf2xH+rrXhztOmjGHJj4jsEqXw== + dependencies: + "@csstools/css-calc" "^1.2.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-unset-value@^1.0.0": version "1.0.2" resolved "https://registry.npmmirror.com/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz#c99bb70e2cdc7312948d1eb41df2412330b81f77" integrity sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g== +"@csstools/postcss-unset-value@^3.0.1": + version "3.0.1" + resolved "https://registry.npmmirror.com/@csstools/postcss-unset-value/-/postcss-unset-value-3.0.1.tgz#598a25630fd9ab0edf066d235916f7441404942a" + integrity sha512-dbDnZ2ja2U8mbPP0Hvmt2RMEGBiF1H7oY6HYSpjteXJGihYwgxgTr6KRbbJ/V6c+4wd51M+9980qG4gKVn5ttg== + +"@csstools/selector-resolve-nested@^1.1.0": + version "1.1.0" + resolved "https://registry.npmmirror.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz#d872f2da402d3ce8bd0cf16ea5f9fba76b18e430" + integrity sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg== + "@csstools/selector-specificity@^2.0.0": version "2.2.0" resolved "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz#2cbcf822bf3764c9658c4d2e568bd0c0cb748016" integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== +"@csstools/selector-specificity@^3.1.1": + version "3.1.1" + resolved "https://registry.npmmirror.com/@csstools/selector-specificity/-/selector-specificity-3.1.1.tgz#63085d2995ca0f0e55aa8b8a07d69bfd48b844fe" + integrity sha512-a7cxGcJ2wIlMFLlh8z2ONm+715QkPHiyJcxwQlKOz/03GPw1COpfhcmC9wm4xlZfp//jWHNNMwzjtqHXVWU9KA== + +"@csstools/utilities@^1.0.0": + version "1.0.0" + resolved "https://registry.npmmirror.com/@csstools/utilities/-/utilities-1.0.0.tgz#42f3c213f2fb929324d465684ab9f46a0febd4bb" + integrity sha512-tAgvZQe/t2mlvpNosA4+CkMiZ2azISW5WPAcdSalZlEjQvUfghHxfQcrCiK/7/CrfAWVxyM88kGFYO82heIGDg== + "@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.6.0", "@ctrl/tinycolor@^3.6.1": version "3.6.1" resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31" @@ -3291,7 +4421,7 @@ dependencies: tslib "^2.0.0" -"@emotion/babel-plugin@^11.11.0", "@emotion/babel-plugin@^11.12.0": +"@emotion/babel-plugin@^11.12.0": version "11.12.0" resolved "https://registry.npmmirror.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2" integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw== @@ -3308,7 +4438,7 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@^11", "@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0": +"@emotion/cache@^11.11.0", "@emotion/cache@^11.13.0": version "11.13.1" resolved "https://registry.npmmirror.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== @@ -3330,7 +4460,7 @@ "@emotion/sheet" "^1.4.0" "@emotion/utils" "^1.4.0" -"@emotion/css@^11", "@emotion/css@^11.11.2", "@emotion/css@^11.7.1": +"@emotion/css@^11.11.2", "@emotion/css@^11.7.1": version "11.13.0" resolved "https://registry.npmmirror.com/@emotion/css/-/css-11.13.0.tgz#3b44f008ce782dafa7cecff75b263af174d0c702" integrity sha512-BUk99ylT+YHl+W/HN7nv1RCTkDYmKKqa1qbvM/qLSQEg61gipuBF5Hptk/2/ERmX2DCv0ccuFGhz9i0KSZOqPg== @@ -3368,41 +4498,31 @@ resolved "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== -"@emotion/react@^11", "@emotion/react@^11.11.0": - version "11.11.1" - resolved "https://registry.npmmirror.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" - integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== +"@emotion/react@^11.11.0", "@emotion/react@^11.11.4": + version "11.13.3" + resolved "https://registry.npmmirror.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4" + integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" + "@emotion/babel-plugin" "^11.12.0" + "@emotion/cache" "^11.13.0" + "@emotion/serialize" "^1.3.1" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1", "@emotion/serialize@^1.1.2", "@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0": - version "1.3.1" - resolved "https://registry.npmmirror.com/@emotion/serialize/-/serialize-1.3.1.tgz#490b660178f43d2de8e92b278b51079d726c05c3" - integrity sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA== +"@emotion/serialize@^1.1.3", "@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0", "@emotion/serialize@^1.3.1": + version "1.3.2" + resolved "https://registry.npmmirror.com/@emotion/serialize/-/serialize-1.3.2.tgz#e1c1a2e90708d5d85d81ccaee2dfeb3cc0cccf7a" + integrity sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA== dependencies: "@emotion/hash" "^0.9.2" "@emotion/memoize" "^0.9.0" "@emotion/unitless" "^0.10.0" - "@emotion/utils" "^1.4.0" + "@emotion/utils" "^1.4.1" csstype "^3.0.2" -"@emotion/server@^11": - version "11.11.0" - resolved "https://registry.npmmirror.com/@emotion/server/-/server-11.11.0.tgz#35537176a2a5ed8aed7801f254828e636ec3bd6e" - integrity sha512-6q89fj2z8VBTx9w93kJ5n51hsmtYuFPtZgnc1L8VzRx9ti4EU6EyvF6Nn1H1x3vcCQCF7u2dB2lY4AYJwUW4PA== - dependencies: - "@emotion/utils" "^1.2.1" - html-tokenize "^2.0.0" - multipipe "^1.0.2" - through "^2.3.8" - "@emotion/sheet@^1.4.0": version "1.4.0" resolved "https://registry.npmmirror.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" @@ -3423,20 +4543,15 @@ resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.npmmirror.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== +"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": + version "1.1.0" + resolved "https://registry.npmmirror.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" + integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== -"@emotion/utils@^1", "@emotion/utils@^1.2.1", "@emotion/utils@^1.4.0": - version "1.4.0" - resolved "https://registry.npmmirror.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd" - integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== - -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.npmmirror.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== +"@emotion/utils@^1.2.1", "@emotion/utils@^1.4.0", "@emotion/utils@^1.4.1": + version "1.4.1" + resolved "https://registry.npmmirror.com/@emotion/utils/-/utils-1.4.1.tgz#b3adbb43de12ee2149541c4f1337d2eb7774f0ad" + integrity sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA== "@emotion/weak-memoize@^0.4.0": version "0.4.0" @@ -4239,10 +5354,10 @@ resolved "https://registry.npmmirror.com/@formily/path/-/path-2.3.0.tgz#1cb71389ad39fd30e323b7f35f96cb35f12e304f" integrity sha512-LbSW50ytAH9wPlZc2VkjRjMukoYWiDAII7HfGkSnkCDQYduhWzvbqpkKOd/W8l5VFrLrl2/7yWkfp5cChv0wjA== -"@formily/path@^2.2.27": - version "2.3.1" - resolved "https://registry.npmmirror.com/@formily/path/-/path-2.3.1.tgz#b7adb91f7d3bb0f2efc90672dda466a0590dcb1b" - integrity sha512-BVo89K5nAFntx02+EV696If1b1bVIm5I1tRPtVyCVIjBIfAgga5hK4k80GZ01Dlk3tpReHpiIbZVg2DNVfw7jA== +"@formily/path@2.3.2", "@formily/path@^2.2.27": + version "2.3.2" + resolved "https://registry.npmmirror.com/@formily/path/-/path-2.3.2.tgz#9385d9ba1eabd5e3ed916f2b1953be6257bb192e" + integrity sha512-KK8h/CupHOs4HIgu9JucqwWvIr8Nbmof++Kby0NdNFHdTN5nAyVzStS8VEPFPGRkQaXV3AH+FVGAxgucmEy4ZA== "@formily/react@2.x", "@formily/react@^2.2.0", "@formily/react@^2.2.27": version "2.3.0" @@ -4270,7 +5385,7 @@ resolved "https://registry.npmmirror.com/@formily/reactive/-/reactive-2.3.0.tgz#95582dfcf97c42410454ef7df0c247514bd4446e" integrity sha512-8QYApPL/GvATIP/9K3UeICJNuCaLq99NLlNLEuBsE7cIk2hFiFhWP7vnLtWBdorqeQZNYZ6lzSuau2Ndogu+Dw== -"@formily/shared@2.3.0", "@formily/shared@2.x", "@formily/shared@^2.2.0", "@formily/shared@^2.2.27": +"@formily/shared@2.3.0": version "2.3.0" resolved "https://registry.npmmirror.com/@formily/shared/-/shared-2.3.0.tgz#4423c3dfad0d1017cab1e3c6c8fd34004e0a9664" integrity sha512-sJiMLBmk4JMPI1CxzLawIc+ERTjcnWOm8u4cpmF+3XABdLNU3CxsIRT9CXiJnrS9ZR+OE12AW7cGp6A1hPm7ZA== @@ -4283,6 +5398,19 @@ pascal-case "^3.1.1" upper-case "^2.0.1" +"@formily/shared@2.x", "@formily/shared@^2.2.0", "@formily/shared@^2.2.27": + version "2.3.2" + resolved "https://registry.npmmirror.com/@formily/shared/-/shared-2.3.2.tgz#1cea158491280a267278e0c9dc4f6cc7a69d4485" + integrity sha512-hb8eL28Dqe4WQzGtxC3h7OwG9VPPBXtfKUfmnRkaBMJ8zn2KmdYOP8YnAkwdO2ZMgxtkOl0hku0Ufm2PwP3ziA== + dependencies: + "@formily/path" "2.3.2" + camel-case "^4.1.1" + lower-case "^2.0.1" + no-case "^3.0.4" + param-case "^3.0.4" + pascal-case "^3.1.1" + upper-case "^2.0.1" + "@formily/validator@2.3.0", "@formily/validator@^2.2.27": version "2.3.0" resolved "https://registry.npmmirror.com/@formily/validator/-/validator-2.3.0.tgz#a04b94f7a18ac1a490c1e273eb0c2acd6662d6c2" @@ -4363,11 +5491,6 @@ kolorist "^1.6.0" local-pkg "^0.4.2" -"@ioredis/commands@^1.1.1": - version "1.2.0" - resolved "https://registry.npmmirror.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" - integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== - "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -5367,6 +6490,34 @@ semver "^7.3.5" tar "^6.1.11" +"@module-federation/runtime-tools@0.5.1": + version "0.5.1" + resolved "https://registry.npmmirror.com/@module-federation/runtime-tools/-/runtime-tools-0.5.1.tgz#1b1f93837159a6bf0c0ba78730d589a5a8f74aa3" + integrity sha512-nfBedkoZ3/SWyO0hnmaxuz0R0iGPSikHZOAZ0N/dVSQaIzlffUo35B5nlC2wgWIc0JdMZfkwkjZRrnuuDIJbzg== + dependencies: + "@module-federation/runtime" "0.5.1" + "@module-federation/webpack-bundler-runtime" "0.5.1" + +"@module-federation/runtime@0.5.1": + version "0.5.1" + resolved "https://registry.npmmirror.com/@module-federation/runtime/-/runtime-0.5.1.tgz#b548a75e2068952ff66ad717cbf73fc921edd5d7" + integrity sha512-xgiMUWwGLWDrvZc9JibuEbXIbhXg6z2oUkemogSvQ4LKvrl/n0kbqP1Blk669mXzyWbqtSp6PpvNdwaE1aN5xQ== + dependencies: + "@module-federation/sdk" "0.5.1" + +"@module-federation/sdk@0.5.1": + version "0.5.1" + resolved "https://registry.npmmirror.com/@module-federation/sdk/-/sdk-0.5.1.tgz#6c0a4053c23fa84db7aae7e4736496c541de7191" + integrity sha512-exvchtjNURJJkpqjQ3/opdbfeT2wPKvrbnGnyRkrwW5o3FH1LaST1tkiNviT6OXTexGaVc2DahbdniQHVtQ7pA== + +"@module-federation/webpack-bundler-runtime@0.5.1": + version "0.5.1" + resolved "https://registry.npmmirror.com/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.5.1.tgz#ef626af0d57e3568c474d66d7d3797366e09cafd" + integrity sha512-mMhRFH0k2VjwHt3Jol9JkUsmI/4XlrAoBG3E0o7HoyoPYv1UFOWyqAflfANcUPgbYpvqmyLzDcO+3IT36LXnrA== + dependencies: + "@module-federation/runtime" "0.5.1" + "@module-federation/sdk" "0.5.1" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.npmmirror.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -6028,10 +7179,10 @@ resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.14.0.tgz#9bc39a5a3a71b81bdb310eba6def5bc3966695b7" integrity sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A== -"@remix-run/router@1.19.2": - version "1.19.2" - resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.19.2.tgz#0c896535473291cb41f152c180bedd5680a3b273" - integrity sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA== +"@remix-run/router@1.21.0": + version "1.21.0" + resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5" + integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA== "@restart/hooks@^0.4.7": version "0.4.15" @@ -6120,6 +7271,81 @@ resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz#601fffee719a1e8447f908aca97864eec23b2784" integrity sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg== +"@rspack/binding-darwin-arm64@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.0.14.tgz#b9d99fb71e047f5300a851614f89cb9d7168db3e" + integrity sha512-dHvlF6T6ctThGDIdvkSdacroA1xlCxfteuppBj8BX/UxzLPr4xsaEtNilfJmFfd2/J02UQyTQauN/9EBuA+YkA== + +"@rspack/binding-darwin-x64@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.0.14.tgz#ddc40886f9a0321349e6be2e9469645ae87bbe36" + integrity sha512-q4Da1Bn/4xTLhhnOkT+fjP2STsSCfp4z03/J/h8tCVG/UYz56Ud3q1UEOK33c5Fxw1C4GlhEh5yYOlSAdxFQLQ== + +"@rspack/binding-linux-arm64-gnu@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.14.tgz#2600e00adf5d1e09d7f0f476b3a01671e20731a7" + integrity sha512-JogYtL3VQS9wJ3p3FNhDqinm7avrMsdwz4erP7YCjD7idob93GYAE7dPrHUzSNVnCBYXRaHJYZHDQs7lKVcYZw== + +"@rspack/binding-linux-arm64-musl@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.14.tgz#8c2624c1426ecc51c9bd4c74c8dcc8d7b0bdbc35" + integrity sha512-qgybhxI/nnoa8CUz7zKTC0Oh37NZt9uRxsSV7+ZYrfxqbrVCoNVuutPpY724uUHy1M6W34kVEm1uT1N4Ka5cZg== + +"@rspack/binding-linux-x64-gnu@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.14.tgz#6636bf658304d246d617f01ae12f4cbe29097a62" + integrity sha512-5vzaDRw3/sGKo3ax/1cU3/cxqNjajwlt2LU288vXNe1/n8oe/pcDfYcTugpOe/A1DqzadanudJszLpFcKsaFtQ== + +"@rspack/binding-linux-x64-musl@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.14.tgz#887c7f26876495f45842ebab874198db394dfd6f" + integrity sha512-4U6QD9xVS1eGme52DuJr6Fg/KdcUfJ+iKwH49Up460dZ/fLvGylnVGA+V0mzPlKi8gfy7NwFuYXZdu3Pwi1YYg== + +"@rspack/binding-win32-arm64-msvc@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.14.tgz#cfde74d44a866ed42501d107349a56b7b0122042" + integrity sha512-SjeYw7qqRHYZ5RPClu+ffKZsShQdU3amA1OwC3M0AS6dbfEcji8482St3Y8Z+QSzYRapCEZij9LMM/9ypEhISg== + +"@rspack/binding-win32-ia32-msvc@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.14.tgz#59532613cea22aa78928fb92b1a35347d780aaab" + integrity sha512-m1gUiVyz3Z3VYIK/Ayo5CVHBjnEeRk9a+KIpKSsq1yhZItnMgjtr4bKabU9vjxalO4UoaSmVzODJI8lJBlnn5Q== + +"@rspack/binding-win32-x64-msvc@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.14.tgz#5bf023ba319dc748e54f8c23bcecab79703dc163" + integrity sha512-Gbeg+bayMF9VP9xmlxySL/TC2XrS6/LZM/pqcNOTLHx6LMG/VXCcmKB0rOZo8MzLXEt8D/lQmQ/B6g7pSaAw0g== + +"@rspack/binding@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/binding/-/binding-1.0.14.tgz#60a04aca4369f7c4ca646e69cbeee75636a418ef" + integrity sha512-0wWqFvr9hkF4LgNPgWfkTU0hhkZAMvOytoCs2p+wDX1Up1E/SgJ1U1JAsCxsl1XtUKm7mRvdWHzJmHbza3y89Q== + optionalDependencies: + "@rspack/binding-darwin-arm64" "1.0.14" + "@rspack/binding-darwin-x64" "1.0.14" + "@rspack/binding-linux-arm64-gnu" "1.0.14" + "@rspack/binding-linux-arm64-musl" "1.0.14" + "@rspack/binding-linux-x64-gnu" "1.0.14" + "@rspack/binding-linux-x64-musl" "1.0.14" + "@rspack/binding-win32-arm64-msvc" "1.0.14" + "@rspack/binding-win32-ia32-msvc" "1.0.14" + "@rspack/binding-win32-x64-msvc" "1.0.14" + +"@rspack/core@1.0.14": + version "1.0.14" + resolved "https://registry.npmmirror.com/@rspack/core/-/core-1.0.14.tgz#7b7305391a488b5ac5dc28a82e9fb02b7ec3e8de" + integrity sha512-xHl23lxJZNjItGc5YuE9alz3yjb56y7EgJmAcBMPHMqgjtUt8rBu4xd/cSUjbr9/lLF9N4hdyoJiPJOFs9LEjw== + dependencies: + "@module-federation/runtime-tools" "0.5.1" + "@rspack/binding" "1.0.14" + "@rspack/lite-tapable" "1.0.1" + caniuse-lite "^1.0.30001616" + +"@rspack/lite-tapable@1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz#d4540a5d28bd6177164bc0ba0bee4bdec0458591" + integrity sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w== + "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" @@ -6631,46 +7857,90 @@ dependencies: "@babel/core" "^7.17.9" +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + "@svgr/babel-plugin-add-jsx-attribute@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz#74a5d648bd0347bda99d82409d87b8ca80b9a1ba" integrity sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ== -"@svgr/babel-plugin-remove-jsx-attribute@*": +"@svgr/babel-plugin-remove-jsx-attribute@*", "@svgr/babel-plugin-remove-jsx-attribute@8.0.0": version "8.0.0" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== -"@svgr/babel-plugin-remove-jsx-empty-expression@*": +"@svgr/babel-plugin-remove-jsx-empty-expression@*", "@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": version "8.0.0" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + "@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz#fb9d22ea26d2bc5e0a44b763d4c46d5d3f596c60" integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + "@svgr/babel-plugin-svg-dynamic-title@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz#01b2024a2b53ffaa5efceaa0bf3e1d5a4c520ce4" integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + "@svgr/babel-plugin-svg-em-dimensions@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz#dd3fa9f5b24eb4f93bcf121c3d40ff5facecb217" integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + "@svgr/babel-plugin-transform-react-native-svg@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz#1d8e945a03df65b601551097d8f5e34351d3d305" integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + "@svgr/babel-plugin-transform-svg-component@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz#48620b9e590e25ff95a80f811544218d27f8a250" integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + "@svgr/babel-preset@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/babel-preset/-/babel-preset-6.5.1.tgz#b90de7979c8843c5c580c7e2ec71f024b49eb828" @@ -6696,6 +7966,25 @@ camelcase "^6.2.0" cosmiconfig "^7.0.1" +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.npmmirror.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + "@svgr/hast-util-to-babel-ast@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz#81800bd09b5bcdb968bf6ee7c863d2288fdb80d2" @@ -6704,6 +7993,16 @@ "@babel/types" "^7.20.0" entities "^4.4.0" +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + "@svgr/plugin-jsx@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz#0e30d1878e771ca753c94e69581c7971542a7072" @@ -6714,6 +8013,15 @@ "@svgr/hast-util-to-babel-ast" "^6.5.1" svg-parser "^2.0.4" +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.npmmirror.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== + dependencies: + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" + "@svgr/plugin-svgo@^6.5.1": version "6.5.1" resolved "https://registry.npmmirror.com/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz#0f91910e988fc0b842f88e0960c2862e022abe84" @@ -6723,6 +8031,20 @@ deepmerge "^4.2.2" svgo "^2.8.0" +"@svgr/webpack@^8.1.0": + version "8.1.0" + resolved "https://registry.npmmirror.com/@svgr/webpack/-/webpack-8.1.0.tgz#16f1b5346f102f89fda6ec7338b96a701d8be0c2" + integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA== + dependencies: + "@babel/core" "^7.21.3" + "@babel/plugin-transform-react-constant-elements" "^7.21.3" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" + "@swc/core-darwin-arm64@1.3.72": version "1.3.72" resolved "https://registry.npmmirror.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.72.tgz#73ab213a2a07bfbf6ff71ce5f5bef7fb6857a032" @@ -7214,10 +8536,10 @@ dependencies: "@types/ms" "*" -"@types/decompress@4.2.4": - version "4.2.4" - resolved "https://registry.npmmirror.com/@types/decompress/-/decompress-4.2.4.tgz#dd2715d3ac1f566d03e6e302d1a26ffab59f8c5c" - integrity sha512-/C8kTMRTNiNuWGl5nEyKbPiMv6HA+0RbEXzFhFBEzASM6+oa4tJro9b8nj7eRlOFfuLdzUU+DS/GPDlvvzMOhA== +"@types/decompress@4.2.7": + version "4.2.7" + resolved "https://registry.npmmirror.com/@types/decompress/-/decompress-4.2.7.tgz#604f69b69d519ecb74dea1ea0829f159b85e1332" + integrity sha512-9z+8yjKr5Wn73Pt17/ldnmQToaFHZxK0N1GHysuk/JIPT8RIdQeoInM01wWPgypRcvb6VH1drjuFpQ4zmY437g== dependencies: "@types/node" "*" @@ -7907,9 +9229,9 @@ integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== "@types/ws@^8.5.5": - version "8.5.10" - resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + version "8.5.12" + resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== dependencies: "@types/node" "*" @@ -8639,11 +9961,6 @@ JSONStream@^1.0.4: jsonparse "^1.2.0" through ">=2.2.7 <3" -abab@^2.0.3, abab@^2.0.5: - version "2.0.6" - resolved "https://registry.npmmirror.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - abbrev@1: version "1.1.1" resolved "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -8669,14 +9986,6 @@ accepts@^1.3.5, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - acorn-import-assertions@^1.9.0: version "1.9.0" resolved "https://registry.npmmirror.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" @@ -8687,11 +9996,6 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - acorn-walk@^8.1.1: version "8.3.1" resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.1.tgz#2f10f5b69329d90ae18c58bf1fa8fccd8b959a43" @@ -8702,12 +10006,7 @@ acorn-walk@^8.3.2: resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.10.0, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.10.0, acorn@^8.4.1, acorn@^8.8.2, acorn@^8.9.0: version "8.11.2" resolved "https://registry.npmmirror.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== @@ -9019,33 +10318,10 @@ antd-mobile-v5-count@^1.0.1: resolved "https://registry.npmmirror.com/antd-mobile-v5-count/-/antd-mobile-v5-count-1.0.1.tgz#85f20c46d1635c24e856bcf5ad55e8c98e44a523" integrity sha512-YGsiEDCPUDz3SzfXi6gLZn/HpeSMW+jgPc4qiYUr1fSopg3hkUie2TnooJdExgfiETHefH3Ggs58He0OVfegLA== -antd-mobile@^5.29.1: - version "5.33.1" - resolved "https://registry.npmmirror.com/antd-mobile/-/antd-mobile-5.33.1.tgz#2b760305e10c50998002cd526c7c4ac6c7dfe426" - integrity sha512-CVoLhVlqyoUYopv8ZqZW64m+c8Nyp1l+AxGsYV8cFhhshyXO4lNjWuLJ4xwL4+2VDy16TW21gCItboWLqWP7Mg== - dependencies: - "@floating-ui/dom" "^1.4.2" - "@rc-component/mini-decimal" "^1.1.0" - "@react-spring/web" "~9.6.1" - "@use-gesture/react" "10.3.0" - ahooks "^3.7.6" - antd-mobile-icons "^0.3.0" - antd-mobile-v5-count "^1.0.1" - classnames "^2.3.2" - dayjs "^1.11.7" - lodash "^4.17.21" - rc-field-form "~1.27.4" - rc-util "^5.30.0" - react-is "^18.2.0" - runes2 "^1.1.2" - staged-components "^1.1.3" - tslib "^2.5.0" - use-sync-external-store "^1.2.0" - -antd-mobile@^5.36.1: - version "5.37.1" - resolved "https://registry.npmmirror.com/antd-mobile/-/antd-mobile-5.37.1.tgz#9a86718d9a95899b7efcb798ca005b59af14b75a" - integrity sha512-r7LXWsvgI13BCwX3k0CPhRXeWVvlpyiION4k6YjoIlkc2NCah0InhVmFSkmbhSU3zufQai5ao0oFhQtVq5dY+Q== +antd-mobile@^5.38: + version "5.38.1" + resolved "https://registry.npmmirror.com/antd-mobile/-/antd-mobile-5.38.1.tgz#0a4592706df6043861990573ec5650e580a87e4f" + integrity sha512-1szLVnmu6hz4iJfKFAsCImJkiLe8FV9IoFChXpnLRBz41wrSfjh7FwPuo0AfFfEuTmV2GYS6BNixiuGscHj+iQ== dependencies: "@floating-ui/dom" "^1.4.2" "@rc-component/mini-decimal" "^1.1.0" @@ -9058,7 +10334,8 @@ antd-mobile@^5.36.1: dayjs "^1.11.7" deepmerge "^4.3.1" nano-memoize "^3.0.16" - rc-field-form "~1.27.4" + rc-field-form "^1.34.2" + rc-segmented "~2.4.1" rc-util "^5.38.1" react-fast-compare "^3.2.2" react-is "^18.2.0" @@ -9067,35 +10344,19 @@ antd-mobile@^5.36.1: tslib "^2.5.0" use-sync-external-store "^1.2.0" -antd-style@3.4.5: - version "3.4.5" - resolved "https://registry.npmmirror.com/antd-style/-/antd-style-3.4.5.tgz#ed1c81a738b3ae9c4079824b13a30c27ab934f4e" - integrity sha512-6aC4P9XyuVy0O7eZ+HZXd8GbbFX9HgzsXsJ341ihJhgqrfsQZNx8lDQvS2kCV6ao99QqtyTDphK9gWOgV2bHEw== +antd-style@3.7.1, antd-style@3.x: + version "3.7.1" + resolved "https://registry.npmmirror.com/antd-style/-/antd-style-3.7.1.tgz#59f35e04c6b4b0082774f9de748d3e946188ba87" + integrity sha512-CQOfddVp4aOvBfCepa+Kj2e7ap+2XBINg1Kn2osdE3oQvrD7KJu/K0sfnLcFLkgCJygbxmuazYdWLKb+drPDYA== dependencies: - "@ant-design/cssinjs" "^1" - "@babel/runtime" "^7" - "@emotion/cache" "^11" - "@emotion/css" "^11" - "@emotion/react" "^11" - "@emotion/serialize" "^1" - "@emotion/server" "^11" - "@emotion/utils" "^1" - use-merge-value "^1" - -antd-style@3.x: - version "3.6.1" - resolved "https://registry.npmmirror.com/antd-style/-/antd-style-3.6.1.tgz#28be830a4cfe62e4915ce3f1ceadc9aab1245feb" - integrity sha512-KpKXiAIV3CAe6TfSh/m6ET2vGuhAof7qfqeaouh5WZ0JDl5jDXXSFPeEfqvZuHAckTl+A0NzArvIHKgKt/NZ9g== - dependencies: - "@ant-design/cssinjs" "^1" - "@babel/runtime" "^7" - "@emotion/cache" "^11" - "@emotion/css" "^11" - "@emotion/react" "^11" - "@emotion/serialize" "^1" - "@emotion/server" "^11" - "@emotion/utils" "^1" - use-merge-value "^1" + "@ant-design/cssinjs" "^1.21.1" + "@babel/runtime" "^7.24.1" + "@emotion/cache" "^11.11.0" + "@emotion/css" "^11.11.2" + "@emotion/react" "^11.11.4" + "@emotion/serialize" "^1.1.3" + "@emotion/utils" "^1.2.1" + use-merge-value "^1.2.0" antd-token-previewer@^2.0.0-alpha.6: version "2.0.5" @@ -9459,6 +10720,18 @@ array-unique@^0.3.2: resolved "https://registry.npmmirror.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== +array.prototype.findlast@^1.2.5: + version "1.2.5" + resolved "https://registry.npmmirror.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + array.prototype.findlastindex@^1.2.5: version "1.2.5" resolved "https://registry.npmmirror.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" @@ -9502,16 +10775,16 @@ array.prototype.reduce@^1.0.6: es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" -array.prototype.tosorted@^1.1.1: - version "1.1.2" - resolved "https://registry.npmmirror.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz#620eff7442503d66c799d95503f82b475745cefd" - integrity sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg== +array.prototype.tosorted@^1.1.1, array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.npmmirror.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - es-shim-unscopables "^1.0.0" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" arraybuffer.prototype.slice@^1.0.2: version "1.0.2" @@ -9661,13 +10934,6 @@ async@^3.2.0, async@^3.2.3, async@^3.2.4, async@~3.2.0: resolved "https://registry.npmmirror.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== -asynciterator.prototype@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" - integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== - dependencies: - has-symbols "^1.0.3" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -9704,6 +10970,18 @@ auto-changelog@^2.4.0: parse-github-url "^1.0.2" semver "^7.3.5" +autoprefixer@^10.4.19: + version "10.4.20" + resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b" + integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g== + dependencies: + browserslist "^4.23.3" + caniuse-lite "^1.0.30001646" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.1" + postcss-value-parser "^4.2.0" + autoprefixer@^10.4.6: version "10.4.16" resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" @@ -9766,13 +11044,22 @@ axios@^0.21.0: dependencies: follow-redirects "^1.14.0" -axios@^0.26, axios@^0.26.1: +axios@^0.26: version "0.26.1" resolved "https://registry.npmmirror.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== dependencies: follow-redirects "^1.14.8" +axios@^1.7.0: + version "1.7.7" + resolved "https://registry.npmmirror.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + b4a@^1.6.4: version "1.6.4" resolved "https://registry.npmmirror.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" @@ -10209,11 +11496,6 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.npmmirror.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.npmmirror.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -10295,6 +11577,16 @@ browserslist@^4.23.1, browserslist@^4.23.3: node-releases "^2.0.18" update-browserslist-db "^1.1.0" +browserslist@^4.24.0, browserslist@^4.24.2: + version "4.24.2" + resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== + dependencies: + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" + node-releases "^2.0.18" + update-browserslist-db "^1.1.1" + bser@2.1.1: version "2.1.1" resolved "https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -10340,11 +11632,6 @@ buffer-from@^1.0.0, buffer-from@^1.1.1: resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer-from@~0.1.1: - version "0.1.2" - resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0" - integrity sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg== - buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -10650,6 +11937,11 @@ caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565: resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001570.tgz#b4e5c1fa786f733ab78fc70f592df6b3f23244ca" integrity sha512-+3e0ASu4sw1SWaoCtvPeyXp+5PsjigkSt8OXZbF9StH5pQWbxEjLAZE3n8Aup5udop1uRiKA7a4utUk/uoSpUw== +caniuse-lite@^1.0.30001616, caniuse-lite@^1.0.30001669: + version "1.0.30001680" + resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e" + integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== + caniuse-lite@^1.0.30001646: version "1.0.30001651" resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" @@ -11120,7 +12412,7 @@ clsx@^1.2.1: resolved "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== -cluster-key-slot@1.1.2, cluster-key-slot@^1.1.0: +cluster-key-slot@1.1.2: version "1.1.2" resolved "https://registry.npmmirror.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== @@ -11736,6 +13028,13 @@ core-js-compat@^3.37.1, core-js-compat@^3.38.0: dependencies: browserslist "^4.23.3" +core-js-compat@^3.38.1: + version "3.39.0" + resolved "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.39.0.tgz#b12dccb495f2601dc860bdbe7b4e3ffa8ba63f61" + integrity sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw== + dependencies: + browserslist "^4.24.2" + core-js-pure@^3.23.3: version "3.34.0" resolved "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.34.0.tgz#981e462500708664c91b827a75b011f04a8134a0" @@ -11812,6 +13111,16 @@ cosmiconfig@^7, cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^8.1.3, cosmiconfig@^8.3.5: + version "8.3.6" + resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + crc-32@^1.2.0, crc-32@~1.2.0: version "1.2.2" resolved "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" @@ -11973,6 +13282,13 @@ css-blank-pseudo@^3.0.3: dependencies: postcss-selector-parser "^6.0.9" +css-blank-pseudo@^6.0.2: + version "6.0.2" + resolved "https://registry.npmmirror.com/css-blank-pseudo/-/css-blank-pseudo-6.0.2.tgz#50db072d4fb5b40c2df9ffe5ca5fbb9b19c77fc8" + integrity sha512-J/6m+lsqpKPqWHOifAFtKFeGLOzw3jR92rxQcwRUfA/eTuZzKfKlxOmYDx2+tqOPQAueNvBiY8WhAeHu5qNmTg== + dependencies: + postcss-selector-parser "^6.0.13" + css-box-model@^1.2.0: version "1.2.1" resolved "https://registry.npmmirror.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" @@ -11992,6 +13308,15 @@ css-has-pseudo@^3.0.4: dependencies: postcss-selector-parser "^6.0.9" +css-has-pseudo@^6.0.5: + version "6.0.5" + resolved "https://registry.npmmirror.com/css-has-pseudo/-/css-has-pseudo-6.0.5.tgz#372e7293ef9bb901ec0bdce85a6fc1365012fa2c" + integrity sha512-ZTv6RlvJJZKp32jPYnAJVhowDCrRrHUTAxsYSuUPBEDJjzws6neMnzkRblxtgmv1RgcV5dhH2gn7E3wA9Wt6lw== + dependencies: + "@csstools/selector-specificity" "^3.1.1" + postcss-selector-parser "^6.0.13" + postcss-value-parser "^4.2.0" + css-loader@6.7.1: version "6.7.1" resolved "https://registry.npmmirror.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" @@ -12006,11 +13331,30 @@ css-loader@6.7.1: postcss-value-parser "^4.2.0" semver "^7.3.5" +css-loader@^6.8.1: + version "6.11.0" + resolved "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" + integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.33" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.5.4" + css-prefers-color-scheme@^6.0.3: version "6.0.3" resolved "https://registry.npmmirror.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" integrity sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA== +css-prefers-color-scheme@^9.0.1: + version "9.0.1" + resolved "https://registry.npmmirror.com/css-prefers-color-scheme/-/css-prefers-color-scheme-9.0.1.tgz#30fcb94cc38b639b66fb99e1882ffd97f741feaa" + integrity sha512-iFit06ochwCKPRiWagbTa1OAWCvWWVdEnIFd8BaRrgO8YrrNh4RAWUQTFcYX5tdFZgFl1DJ3iiULchZyEbnF4g== + css-select-base-adapter@^0.1.1: version "0.1.1" resolved "https://registry.npmmirror.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" @@ -12037,6 +13381,17 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + css-to-react-native@^3.0.0: version "3.2.0" resolved "https://registry.npmmirror.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" @@ -12062,12 +13417,28 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.npmmirror.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.npmmirror.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== -css-what@^6.0.1: +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== @@ -12091,6 +13462,11 @@ cssdb@^6.6.1: resolved "https://registry.npmmirror.com/cssdb/-/cssdb-6.6.3.tgz#1f331a2fab30c18d9f087301e6122a878bb1e505" integrity sha512-7GDvDSmE+20+WcSMhP17Q1EVWUrLlbxxpMDqG731n8P99JhnQZHR9YvtjPvEHfjFUjvQJvdpKCjlKOX+xe4UVA== +cssdb@^8.1.0: + version "8.2.1" + resolved "https://registry.npmmirror.com/cssdb/-/cssdb-8.2.1.tgz#62a5d9a41e2c86f1d7c35981098fc5ce47c5766c" + integrity sha512-KwEPys7lNsC8OjASI8RrmwOYYDcm0JOW9zQhcV83ejYcQkirTEyeAGui8aO2F5PiS6SLpxuTzl6qlMElIdsgIg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -12103,27 +13479,19 @@ csso@^4.0.2, csso@^4.2.0: dependencies: css-tree "^1.1.2" -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.npmmirror.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.npmmirror.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.npmmirror.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== dependencies: - cssom "~0.3.6" + css-tree "~2.2.0" -csstype@3.1.2: - version "3.1.2" - resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +cssstyle@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/cssstyle/-/cssstyle-4.1.0.tgz#161faee382af1bafadb6d3867a92a19bcb4aea70" + integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== + dependencies: + rrweb-cssom "^0.7.1" csstype@^3.0.2, csstype@^3.0.8, csstype@^3.1.3: version "3.1.3" @@ -12514,14 +13882,13 @@ data-uri-to-buffer@^6.0.0: resolved "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz#540bd4c8753a25ee129035aebdedf63b078703c7" integrity sha512-MZd3VlchQkp8rdend6vrx7MmVDJzSNTBvghvKjirLkD+WTChA3KUf0jkE68Q4UyctNqI11zZO9/x2Yx+ub5Cvg== -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" data-view-buffer@^1.0.1: version "1.0.1" @@ -12650,7 +14017,7 @@ decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0: resolved "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js@^10.2.1, decimal.js@^10.3.1: +decimal.js@^10.3.1, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -13162,13 +14529,6 @@ domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== - dependencies: - webidl-conversions "^5.0.0" - domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -13353,13 +14713,6 @@ dumi@2.2.14: v8-compile-cache "2.3.0" vfile "^5.3.7" -duplexer2@^0.1.2: - version "0.1.4" - resolved "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== - dependencies: - readable-stream "^2.0.2" - duplexer3@^0.1.4: version "0.1.5" resolved "https://registry.npmmirror.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" @@ -13448,6 +14801,11 @@ electron-to-chromium@^1.5.4: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz#c81d9938b5a877314ad370feb73b4e5409b36abd" integrity sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw== +electron-to-chromium@^1.5.41: + version "1.5.59" + resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.59.tgz#220d48099bf51dfb804bbb2386592c16c232de38" + integrity sha512-faAXB6+gEbC8FsiRdpOXgOe4snP49YwjiXynEB8Mp7sUx80W5eN+BnnBHJ/F7eIeLzs+QBfDD40bJMm97oEFcw== + elkjs@^0.8.2: version "0.8.2" resolved "https://registry.npmmirror.com/elkjs/-/elkjs-0.8.2.tgz#c37763c5a3e24e042e318455e0147c912a7c248e" @@ -13564,7 +14922,7 @@ entities@^2.0.0: resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.2.0, entities@^4.4.0: +entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -13660,7 +15018,7 @@ es-abstract@^1.17.2, es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" -es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: +es-abstract@^1.17.5, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.3: version "1.23.3" resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== @@ -13744,25 +15102,26 @@ es-get-iterator@^1.1.3: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-iterator-helpers@^1.0.12: - version "1.0.15" - resolved "https://registry.npmmirror.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" - integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== +es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.1.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz#2f1a3ab998b30cb2d10b195b587c6d9ebdebf152" + integrity sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q== dependencies: - asynciterator.prototype "^1.0.0" - call-bind "^1.0.2" + call-bind "^1.0.7" define-properties "^1.2.1" - es-abstract "^1.22.1" - es-set-tostringtag "^2.0.1" - function-bind "^1.1.1" - get-intrinsic "^1.2.1" - globalthis "^1.0.3" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-set-tostringtag "^2.0.3" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + globalthis "^1.0.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + has-proto "^1.0.3" has-symbols "^1.0.3" - internal-slot "^1.0.5" - iterator.prototype "^1.1.2" - safe-array-concat "^1.0.1" + internal-slot "^1.0.7" + iterator.prototype "^1.1.3" + safe-array-concat "^1.1.2" es-object-atoms@^1.0.0: version "1.0.0" @@ -14024,6 +15383,11 @@ escalade@^3.1.2: resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -14049,7 +15413,7 @@ escape-string-regexp@^5.0.0: resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== -escodegen@^2.0.0, escodegen@^2.1.0: +escodegen@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== @@ -14166,7 +15530,7 @@ eslint-plugin-react-hooks@4.6.0, eslint-plugin-react-hooks@^4.6.0: resolved "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== -eslint-plugin-react@7.33.2, eslint-plugin-react@^7.33.0: +eslint-plugin-react@7.33.2: version "7.33.2" resolved "https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== @@ -14188,6 +15552,30 @@ eslint-plugin-react@7.33.2, eslint-plugin-react@^7.33.0: semver "^6.3.1" string.prototype.matchall "^4.0.8" +eslint-plugin-react@^7.33.0: + version "7.37.2" + resolved "https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz#cd0935987876ba2900df2f58339f6d92305acc7a" + integrity sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w== + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.2" + array.prototype.tosorted "^1.1.4" + doctrine "^2.1.0" + es-iterator-helpers "^1.1.0" + estraverse "^5.3.0" + hasown "^2.0.2" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.8" + object.fromentries "^2.0.8" + object.values "^1.2.0" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.11" + string.prototype.repeat "^1.0.0" + eslint-plugin-testing-library@^5.11.0: version "5.11.1" resolved "https://registry.npmmirror.com/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz#5b46cdae96d4a78918711c0b4792f90088e62d20" @@ -15082,11 +16470,16 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -follow-redirects@^1.14.0, follow-redirects@^1.14.8: +follow-redirects@^1.14.0: version "1.15.3" resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== +follow-redirects@^1.14.8, follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3, for-each@~0.3.3: version "0.3.3" resolved "https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -15205,7 +16598,7 @@ frac@~1.1.2: resolved "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b" integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== -fraction.js@^4.2.0, fraction.js@^4.3.6: +fraction.js@^4.2.0, fraction.js@^4.3.6, fraction.js@^4.3.7: version "4.3.7" resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== @@ -15355,7 +16748,7 @@ fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2, fsevents@~2.3.3: resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.1, function-bind@^1.1.2: +function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== @@ -15878,6 +17271,14 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + globby@^11.0.2, globby@^11.1.0: version "11.1.0" resolved "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -16037,7 +17438,7 @@ handlebars-utils@^1.0.6: kind-of "^6.0.0" typeof-article "^0.1.1" -handlebars@^4.7.7: +handlebars@^4.7.7, handlebars@^4.7.8: version "4.7.8" resolved "https://registry.npmmirror.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== @@ -16483,12 +17884,12 @@ html-comment-regex@^1.1.2: resolved "https://registry.npmmirror.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== dependencies: - whatwg-encoding "^1.0.5" + whatwg-encoding "^3.1.1" html-entities@^2.1.0: version "2.4.0" @@ -16539,17 +17940,6 @@ html-to-text@^9.0.5: htmlparser2 "^8.0.2" selderee "^0.11.0" -html-tokenize@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/html-tokenize/-/html-tokenize-2.0.1.tgz#c3b2ea6e2837d4f8c06693393e9d2a12c960be5f" - integrity sha512-QY6S+hZ0f5m1WT8WffYN+Hg+xm/w5I8XeUcAq/ZYP5wVC8xbKi4Whhru3FtrAebD5EhBW8rmFzkDI6eCAuFe2w== - dependencies: - buffer-from "~0.1.1" - inherits "~2.0.1" - minimist "~1.2.5" - readable-stream "~1.0.27-1" - through2 "~0.4.1" - html-void-elements@^2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f" @@ -16677,10 +18067,10 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz#e9096c5afd071a3fce56e6252bb321583c124673" - integrity sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ== +http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== dependencies: agent-base "^7.1.0" debug "^4.3.4" @@ -16715,7 +18105,7 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^7.0.0: +https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.2, https-proxy-agent@^7.0.5: version "7.0.5" resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== @@ -16723,14 +18113,6 @@ https-proxy-agent@^7.0.0: agent-base "^7.0.2" debug "4" -https-proxy-agent@^7.0.2: - version "7.0.2" - resolved "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz#e2645b846b90e96c6e6f347fb5b2e41f1590b09b" - integrity sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA== - dependencies: - agent-base "^7.0.2" - debug "4" - httpx@^2.2.0, httpx@^2.2.6: version "2.3.1" resolved "https://registry.npmmirror.com/httpx/-/httpx-2.3.1.tgz#e4c7a07dd7f9458810f3ae80eb13e3afcc75500b" @@ -16792,7 +18174,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.15, iconv-lite@^0.4.24, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.6, iconv-lite@^0.6.2, iconv-lite@^0.6.3: +iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.2, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -16845,12 +18227,17 @@ image-size@~0.5.0: resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== +immer@^10.1.1: + version "10.1.1" + resolved "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc" + integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== + immutable@^4.0.0: version "4.3.4" resolved "https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== -import-fresh@^3.0.0, import-fresh@^3.2.1: +import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -17071,21 +18458,6 @@ invert-kv@^1.0.0: resolved "https://registry.npmmirror.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" integrity sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ== -ioredis@^5.4.1: - version "5.4.1" - resolved "https://registry.npmmirror.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40" - integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA== - dependencies: - "@ioredis/commands" "^1.1.1" - cluster-key-slot "^1.1.0" - debug "^4.3.4" - denque "^2.1.0" - lodash.defaults "^4.2.0" - lodash.isarguments "^3.1.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^2.1.0" - ip@^1.1.4, ip@^1.1.5, ip@^1.1.8: version "1.1.8" resolved "https://registry.npmmirror.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" @@ -17881,10 +19253,10 @@ istextorbinary@^2.2.1: editions "^2.2.0" textextensions "^2.5.0" -iterator.prototype@^1.1.2: - version "1.1.2" - resolved "https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" - integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== +iterator.prototype@^1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.3.tgz#016c2abe0be3bbdb8319852884f60908ac62bf9c" + integrity sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ== dependencies: define-properties "^1.2.1" get-intrinsic "^1.2.1" @@ -17970,6 +19342,11 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" +jiti@^1.20.0: + version "1.21.6" + resolved "https://registry.npmmirror.com/jiti/-/jiti-1.21.6.tgz#6c7f7398dd4b3142767f9a168af2f317a428d268" + integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== + jmespath@^0.16.0: version "0.16.0" resolved "https://registry.npmmirror.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" @@ -18048,38 +19425,32 @@ jsdom-worker@^0.3.0: mitt "^3.0.0" uuid-v4 "^0.1.0" -jsdom@^16.0.0: - version "16.7.0" - resolved "https://registry.npmmirror.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== +jsdom@^25.0.1: + version "25.0.1" + resolved "https://registry.npmmirror.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" + integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" + cssstyle "^4.1.0" + data-urls "^5.0.0" + decimal.js "^10.4.3" + form-data "^4.0.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" + nwsapi "^2.2.12" + parse5 "^7.1.2" + rrweb-cssom "^0.7.1" + saxes "^6.0.0" symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" + tough-cookie "^5.0.0" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + ws "^8.18.0" + xml-name-validator "^5.0.0" jsep@^1.3.8: version "1.3.9" @@ -18091,6 +19462,11 @@ jsesc@^2.5.1: resolved "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2, jsesc@~3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" @@ -18394,6 +19770,11 @@ kleur@^4.0.3: resolved "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780" integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== +klona@^2.0.4: + version "2.0.6" + resolved "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + koa-bodyparser@^4.3.0: version "4.4.1" resolved "https://registry.npmmirror.com/koa-bodyparser/-/koa-bodyparser-4.4.1.tgz#a908d848e142cc57d9eece478e932bf00dce3029" @@ -18584,6 +19965,13 @@ lerna@^4.0.0: import-local "^3.0.2" npmlog "^4.1.2" +less-loader@11.1.0: + version "11.1.0" + resolved "https://registry.npmmirror.com/less-loader/-/less-loader-11.1.0.tgz#a452384259bdf8e4f6d5fdcc39543609e6313f82" + integrity sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug== + dependencies: + klona "^2.0.4" + less-plugin-resolve@1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/less-plugin-resolve/-/less-plugin-resolve-1.0.2.tgz#da86d9ea78d762008d1221e619c99fe616d772c8" @@ -18608,6 +19996,23 @@ less@4.1.3: needle "^3.1.0" source-map "~0.6.0" +less@^4.1.3: + version "4.2.0" + resolved "https://registry.npmmirror.com/less/-/less-4.2.0.tgz#cbefbfaa14a4cd388e2099b2b51f956e1465c450" + integrity sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^3.1.0" + source-map "~0.6.0" + levn@^0.4.1: version "0.4.1" resolved "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -18932,11 +20337,6 @@ lodash.indexof@^4.0.5: resolved "https://registry.npmmirror.com/lodash.indexof/-/lodash.indexof-4.0.5.tgz#53714adc2cddd6ed87638f893aa9b6c24e31ef3c" integrity sha512-t9wLWMQsawdVmf6/IcAgVGqAJkNzYVcn4BHYZKTPW//l7N5Oq7Bq138BaVk19agcsPZePcidSgTTw4NqS1nUAw== -lodash.isarguments@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== - lodash.isboolean@^3.0.3: version "3.0.3" resolved "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" @@ -19600,6 +21000,16 @@ mdn-data@2.0.14: resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -20186,7 +21596,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.5, minimist@~1.2.8: +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.8: version "1.2.8" resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -20508,14 +21918,6 @@ multimatch@^5.0.0: arrify "^2.0.1" minimatch "^3.0.4" -multipipe@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/multipipe/-/multipipe-1.0.2.tgz#cc13efd833c9cda99f224f868461b8e1a3fd939d" - integrity sha512-6uiC9OvY71vzSGX8lZvSqscE7ft9nPupJ8fMjrCNRAUy2LREUW42UL+V/NTrogr6rFgRydUrCX4ZitfpSNkSCQ== - dependencies: - duplexer2 "^0.1.2" - object-assign "^4.1.0" - mute-stdout@^1.0.0: version "1.0.1" resolved "https://registry.npmmirror.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" @@ -21099,7 +22501,7 @@ number-is-nan@^1.0.0: resolved "https://registry.npmmirror.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ== -nwsapi@2.2.7, nwsapi@^2.2.0: +nwsapi@2.2.7, nwsapi@^2.2.12: version "2.2.7" resolved "https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== @@ -21156,11 +22558,6 @@ object-keys@^1.1.1: resolved "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-keys@~0.4.0: - version "0.4.0" - resolved "https://registry.npmmirror.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" - integrity sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw== - object-path@^0.11.8: version "0.11.8" resolved "https://registry.npmmirror.com/object-path/-/object-path-0.11.8.tgz#ed002c02bbdd0070b78a27455e8ae01fc14d4742" @@ -21193,14 +22590,14 @@ object.defaults@^1.0.0, object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.6, object.entries@^1.1.7: - version "1.1.7" - resolved "https://registry.npmmirror.com/object.entries/-/object.entries-1.1.7.tgz#2b47760e2a2e3a752f39dd874655c61a7f03c131" - integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== +object.entries@^1.1.6, object.entries@^1.1.7, object.entries@^1.1.8: + version "1.1.8" + resolved "https://registry.npmmirror.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" + integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" object.fromentries@^2.0.6, object.fromentries@^2.0.8: version "2.0.8" @@ -21851,7 +23248,7 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse-json@^5.0.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -21896,17 +23293,17 @@ parse-url@^6.0.0: parse-path "^4.0.0" protocols "^1.4.0" -parse5@6.0.1, parse5@^6.0.0: +parse5@^6.0.0: version "6.0.1" resolved "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== -parse5@^7.0.0: - version "7.1.2" - resolved "https://registry.npmmirror.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" - integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== +parse5@^7.0.0, parse5@^7.1.2: + version "7.2.1" + resolved "https://registry.npmmirror.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== dependencies: - entities "^4.4.0" + entities "^4.5.0" parseley@^0.12.0: version "0.12.1" @@ -22205,6 +23602,11 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== +picocolors@^1.1.0, picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -22476,6 +23878,13 @@ postcss-attribute-case-insensitive@^5.0.0: dependencies: postcss-selector-parser "^6.0.10" +postcss-attribute-case-insensitive@^6.0.3: + version "6.0.3" + resolved "https://registry.npmmirror.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.3.tgz#d118023911a768dfccfc0b0147f5ff06d8485806" + integrity sha512-KHkmCILThWBRtg+Jn1owTnHPnFit4OkqS+eKiGEOPIGke54DCeYGJ6r0Fx/HjfE9M9kznApCLcU0DvnPchazMQ== + dependencies: + postcss-selector-parser "^6.0.13" + postcss-clamp@^4.1.0: version "4.1.0" resolved "https://registry.npmmirror.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" @@ -22490,6 +23899,17 @@ postcss-color-functional-notation@^4.2.2: dependencies: postcss-value-parser "^4.2.0" +postcss-color-functional-notation@^6.0.14: + version "6.0.14" + resolved "https://registry.npmmirror.com/postcss-color-functional-notation/-/postcss-color-functional-notation-6.0.14.tgz#958d8fc434fafbb15ebc7964053f19d366773078" + integrity sha512-dNUX+UH4dAozZ8uMHZ3CtCNYw8fyFAmqqdcyxMr7PEdM9jLXV19YscoYO0F25KqZYhmtWKQ+4tKrIZQrwzwg7A== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + postcss-color-hex-alpha@^8.0.3: version "8.0.4" resolved "https://registry.npmmirror.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz#c66e2980f2fbc1a63f5b079663340ce8b55f25a5" @@ -22497,6 +23917,14 @@ postcss-color-hex-alpha@^8.0.3: dependencies: postcss-value-parser "^4.2.0" +postcss-color-hex-alpha@^9.0.4: + version "9.0.4" + resolved "https://registry.npmmirror.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.4.tgz#f455902fb222453b2eb9699dfa9fc17a9c056f1e" + integrity sha512-XQZm4q4fNFqVCYMGPiBjcqDhuG7Ey2xrl99AnDJMyr5eDASsAGalndVgHZF8i97VFNy1GQeZc4q2ydagGmhelQ== + dependencies: + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + postcss-color-rebeccapurple@^7.0.2: version "7.1.1" resolved "https://registry.npmmirror.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz#63fdab91d878ebc4dd4b7c02619a0c3d6a56ced0" @@ -22504,6 +23932,24 @@ postcss-color-rebeccapurple@^7.0.2: dependencies: postcss-value-parser "^4.2.0" +postcss-color-rebeccapurple@^9.0.3: + version "9.0.3" + resolved "https://registry.npmmirror.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-9.0.3.tgz#63e14d9b9ab196e62e3491606a2b77a9531a6825" + integrity sha512-ruBqzEFDYHrcVq3FnW3XHgwRqVMrtEPLBtD7K2YmsLKVc2jbkxzzNEctJKsPCpDZ+LeMHLKRDoSShVefGc+CkQ== + dependencies: + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + +postcss-custom-media@^10.0.8: + version "10.0.8" + resolved "https://registry.npmmirror.com/postcss-custom-media/-/postcss-custom-media-10.0.8.tgz#0b84916522eb1e8a4b9e3ecd2bce292844cd7323" + integrity sha512-V1KgPcmvlGdxTel4/CyQtBJEFhMVpEmRGFrnVtgfGIHj5PJX9vO36eFBxKBeJn+aCDTed70cc+98Mz3J/uVdGQ== + dependencies: + "@csstools/cascade-layer-name-parser" "^1.0.13" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/media-query-list-parser" "^2.1.13" + postcss-custom-media@^8.0.0: version "8.0.2" resolved "https://registry.npmmirror.com/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz#c8f9637edf45fef761b014c024cee013f80529ea" @@ -22518,6 +23964,17 @@ postcss-custom-properties@^12.1.7: dependencies: postcss-value-parser "^4.2.0" +postcss-custom-properties@^13.3.12: + version "13.3.12" + resolved "https://registry.npmmirror.com/postcss-custom-properties/-/postcss-custom-properties-13.3.12.tgz#e21960c7d13aed960b28236412d4da67f75317b0" + integrity sha512-oPn/OVqONB2ZLNqN185LDyaVByELAA/u3l2CS2TS16x2j2XsmV4kd8U49+TMxmUsEU9d8fB/I10E6U7kB0L1BA== + dependencies: + "@csstools/cascade-layer-name-parser" "^1.0.13" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + postcss-custom-selectors@^6.0.0: version "6.0.3" resolved "https://registry.npmmirror.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz#1ab4684d65f30fed175520f82d223db0337239d9" @@ -22525,6 +23982,16 @@ postcss-custom-selectors@^6.0.0: dependencies: postcss-selector-parser "^6.0.4" +postcss-custom-selectors@^7.1.12: + version "7.1.12" + resolved "https://registry.npmmirror.com/postcss-custom-selectors/-/postcss-custom-selectors-7.1.12.tgz#4d1bac2469003aad3aa3d73481a1b7a45290852b" + integrity sha512-ctIoprBMJwByYMGjXG0F7IT2iMF2hnamQ+aWZETyBM0aAlyaYdVZTeUkk8RB+9h9wP+NdN3f01lfvKl2ZSqC0g== + dependencies: + "@csstools/cascade-layer-name-parser" "^1.0.13" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + postcss-selector-parser "^6.1.0" + postcss-dir-pseudo-class@^6.0.4: version "6.0.5" resolved "https://registry.npmmirror.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz#2bf31de5de76added44e0a25ecf60ae9f7c7c26c" @@ -22532,6 +23999,13 @@ postcss-dir-pseudo-class@^6.0.4: dependencies: postcss-selector-parser "^6.0.10" +postcss-dir-pseudo-class@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-8.0.1.tgz#b93755f52fb90215301b1d3ecb7c5e6416930a1e" + integrity sha512-uULohfWBBVoFiZXgsQA24JV6FdKIidQ+ZqxOouhWwdE+qJlALbkS5ScB43ZTjPK+xUZZhlaO/NjfCt5h4IKUfw== + dependencies: + postcss-selector-parser "^6.0.13" + postcss-double-position-gradients@^3.1.1: version "3.1.2" resolved "https://registry.npmmirror.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz#b96318fdb477be95997e86edd29c6e3557a49b91" @@ -22540,6 +24014,15 @@ postcss-double-position-gradients@^3.1.1: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" +postcss-double-position-gradients@^5.0.7: + version "5.0.7" + resolved "https://registry.npmmirror.com/postcss-double-position-gradients/-/postcss-double-position-gradients-5.0.7.tgz#1a4841daf7ac04e94de4672282e8d02d1b3dd274" + integrity sha512-1xEhjV9u1s4l3iP5lRt1zvMjI/ya8492o9l/ivcxHhkO3nOz16moC4JpMxDUGrOs4R3hX+KWT7gKoV842cwRgg== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + postcss-env-function@^4.0.6: version "4.0.6" resolved "https://registry.npmmirror.com/postcss-env-function/-/postcss-env-function-4.0.6.tgz#7b2d24c812f540ed6eda4c81f6090416722a8e7a" @@ -22559,6 +24042,13 @@ postcss-focus-visible@^6.0.4: dependencies: postcss-selector-parser "^6.0.9" +postcss-focus-visible@^9.0.1: + version "9.0.1" + resolved "https://registry.npmmirror.com/postcss-focus-visible/-/postcss-focus-visible-9.0.1.tgz#eede1032ce86b3bb2556d93ca5df63c68dfc2559" + integrity sha512-N2VQ5uPz3Z9ZcqI5tmeholn4d+1H14fKXszpjogZIrFbhaq0zNAtq8sAnw6VLiqGbL8YBzsnu7K9bBkTqaRimQ== + dependencies: + postcss-selector-parser "^6.0.13" + postcss-focus-within@^5.0.4: version "5.0.4" resolved "https://registry.npmmirror.com/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz#5b1d2ec603195f3344b716c0b75f61e44e8d2e20" @@ -22566,6 +24056,13 @@ postcss-focus-within@^5.0.4: dependencies: postcss-selector-parser "^6.0.9" +postcss-focus-within@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/postcss-focus-within/-/postcss-focus-within-8.0.1.tgz#524af4c7eabae35cb1efa220a7903016fcc897fa" + integrity sha512-NFU3xcY/xwNaapVb+1uJ4n23XImoC86JNwkY/uduytSl2s9Ekc2EpzmRR63+ExitnW3Mab3Fba/wRPCT5oDILA== + dependencies: + postcss-selector-parser "^6.0.13" + postcss-font-variant@^5.0.0: version "5.0.0" resolved "https://registry.npmmirror.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" @@ -22576,6 +24073,11 @@ postcss-gap-properties@^3.0.3: resolved "https://registry.npmmirror.com/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz#f7e3cddcf73ee19e94ccf7cb77773f9560aa2fff" integrity sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg== +postcss-gap-properties@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/postcss-gap-properties/-/postcss-gap-properties-5.0.1.tgz#887b64655f42370b43f0ab266cc6dbabf504d276" + integrity sha512-k2z9Cnngc24c0KF4MtMuDdToROYqGMMUQGcE6V0odwjHyOHtaDBlLeRBV70y9/vF7KIbShrTRZ70JjsI1BZyWw== + postcss-image-set-function@^4.0.6: version "4.0.7" resolved "https://registry.npmmirror.com/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz#08353bd756f1cbfb3b6e93182c7829879114481f" @@ -22583,6 +24085,14 @@ postcss-image-set-function@^4.0.6: dependencies: postcss-value-parser "^4.2.0" +postcss-image-set-function@^6.0.3: + version "6.0.3" + resolved "https://registry.npmmirror.com/postcss-image-set-function/-/postcss-image-set-function-6.0.3.tgz#84c5e32cc1085198f2cf4a786028dae8a2632bb2" + integrity sha512-i2bXrBYzfbRzFnm+pVuxVePSTCRiNmlfssGI4H0tJQvDue+yywXwUxe68VyzXs7cGtMaH6MCLY6IbCShrSroCw== + dependencies: + "@csstools/utilities" "^1.0.0" + postcss-value-parser "^4.2.0" + postcss-initial@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" @@ -22596,6 +24106,17 @@ postcss-lab-function@^4.2.0: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" +postcss-lab-function@^6.0.19: + version "6.0.19" + resolved "https://registry.npmmirror.com/postcss-lab-function/-/postcss-lab-function-6.0.19.tgz#09b04c016bfbacd8576988a73dc19c0fdbeae2c4" + integrity sha512-vwln/mgvFrotJuGV8GFhpAOu9iGf3pvTBr6dLPDmUcqVD5OsQpEFyQMAFTxSxWXGEzBj6ld4pZ/9GDfEpXvo0g== + dependencies: + "@csstools/css-color-parser" "^2.0.4" + "@csstools/css-parser-algorithms" "^2.7.1" + "@csstools/css-tokenizer" "^2.4.1" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/utilities" "^1.0.0" + postcss-load-config@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-6.0.1.tgz#6fd7dcd8ae89badcf1b2d644489cbabf83aa8096" @@ -22603,11 +24124,27 @@ postcss-load-config@^6.0.1: dependencies: lilconfig "^3.1.1" +postcss-loader@^7.3.3: + version "7.3.4" + resolved "https://registry.npmmirror.com/postcss-loader/-/postcss-loader-7.3.4.tgz#aed9b79ce4ed7e9e89e56199d25ad1ec8f606209" + integrity sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A== + dependencies: + cosmiconfig "^8.3.5" + jiti "^1.20.0" + semver "^7.5.4" + postcss-logical@^5.0.4: version "5.0.4" resolved "https://registry.npmmirror.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" integrity sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g== +postcss-logical@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/postcss-logical/-/postcss-logical-7.0.1.tgz#a3121f6510591b195321b16e65fbe13b1cfd3115" + integrity sha512-8GwUQZE0ri0K0HJHkDv87XOLC8DE0msc+HoWLeKdtjDZEwpZ5xuK3QdV6FhmHSQW40LPkg43QzvATRAI3LsRkg== + dependencies: + postcss-value-parser "^4.2.0" + postcss-media-minmax@^5.0.0: version "5.0.0" resolved "https://registry.npmmirror.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" @@ -22618,6 +24155,11 @@ postcss-modules-extract-imports@^3.0.0: resolved "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== + postcss-modules-local-by-default@^4.0.0: version "4.0.3" resolved "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" @@ -22627,6 +24169,15 @@ postcss-modules-local-by-default@^4.0.0: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" +postcss-modules-local-by-default@^4.0.5: + version "4.1.0" + resolved "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz#b0db6bc81ffc7bdc52eb0f84d6ca0bedf0e36d21" + integrity sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^7.0.0" + postcss-value-parser "^4.1.0" + postcss-modules-scope@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" @@ -22634,6 +24185,13 @@ postcss-modules-scope@^3.0.0: dependencies: postcss-selector-parser "^6.0.4" +postcss-modules-scope@^3.2.0: + version "3.2.1" + resolved "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz#1bbccddcb398f1d7a511e0a2d1d047718af4078c" + integrity sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA== + dependencies: + postcss-selector-parser "^7.0.0" + postcss-modules-values@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" @@ -22649,11 +24207,25 @@ postcss-nesting@^10.1.4: "@csstools/selector-specificity" "^2.0.0" postcss-selector-parser "^6.0.10" +postcss-nesting@^12.1.5: + version "12.1.5" + resolved "https://registry.npmmirror.com/postcss-nesting/-/postcss-nesting-12.1.5.tgz#e5e2dc1d63e6166c194da45aa28c04d4024db98f" + integrity sha512-N1NgI1PDCiAGWPTYrwqm8wpjv0bgDmkYHH72pNsqTCv9CObxjxftdYu6AKtGN+pnJa7FQjMm3v4sp8QJbFsYdQ== + dependencies: + "@csstools/selector-resolve-nested" "^1.1.0" + "@csstools/selector-specificity" "^3.1.1" + postcss-selector-parser "^6.1.0" + postcss-opacity-percentage@^1.1.2: version "1.1.3" resolved "https://registry.npmmirror.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz#5b89b35551a556e20c5d23eb5260fbfcf5245da6" integrity sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A== +postcss-opacity-percentage@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz#c0a56060cd4586e3f954dbde1efffc2deed53002" + integrity sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ== + postcss-overflow-shorthand@^3.0.3: version "3.0.4" resolved "https://registry.npmmirror.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz#7ed6486fec44b76f0eab15aa4866cda5d55d893e" @@ -22661,6 +24233,13 @@ postcss-overflow-shorthand@^3.0.3: dependencies: postcss-value-parser "^4.2.0" +postcss-overflow-shorthand@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-5.0.1.tgz#c0a124edad4f7ad88109275a60510e1fb07ab833" + integrity sha512-XzjBYKLd1t6vHsaokMV9URBt2EwC9a7nDhpQpjoPk2HRTSQfokPfyAS/Q7AOrzUu6q+vp/GnrDBGuj/FCaRqrQ== + dependencies: + postcss-value-parser "^4.2.0" + postcss-page-break@^3.0.4: version "3.0.4" resolved "https://registry.npmmirror.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" @@ -22673,6 +24252,13 @@ postcss-place@^7.0.4: dependencies: postcss-value-parser "^4.2.0" +postcss-place@^9.0.1: + version "9.0.1" + resolved "https://registry.npmmirror.com/postcss-place/-/postcss-place-9.0.1.tgz#c08c46a94e639c1ee3457ac96d50c50a89bd6ac3" + integrity sha512-JfL+paQOgRQRMoYFc2f73pGuG/Aw3tt4vYMR6UA3cWVMxivviPTnMFnFTczUJOA4K2Zga6xgQVE+PcLs64WC8Q== + dependencies: + postcss-value-parser "^4.2.0" + postcss-prefix-selector@1.16.0: version "1.16.0" resolved "https://registry.npmmirror.com/postcss-prefix-selector/-/postcss-prefix-selector-1.16.0.tgz#ad5b56f9a73a2c090ca7161049632c9d89bcb404" @@ -22729,6 +24315,73 @@ postcss-preset-env@7.5.0: postcss-selector-not "^5.0.0" postcss-value-parser "^4.2.0" +postcss-preset-env@^9.1.2: + version "9.6.0" + resolved "https://registry.npmmirror.com/postcss-preset-env/-/postcss-preset-env-9.6.0.tgz#da5fc8606f95092b2788c3bdf6d4fc053e50075b" + integrity sha512-Lxfk4RYjUdwPCYkc321QMdgtdCP34AeI94z+/8kVmqnTIlD4bMRQeGcMZgwz8BxHrzQiFXYIR5d7k/9JMs2MEA== + dependencies: + "@csstools/postcss-cascade-layers" "^4.0.6" + "@csstools/postcss-color-function" "^3.0.19" + "@csstools/postcss-color-mix-function" "^2.0.19" + "@csstools/postcss-content-alt-text" "^1.0.0" + "@csstools/postcss-exponential-functions" "^1.0.9" + "@csstools/postcss-font-format-keywords" "^3.0.2" + "@csstools/postcss-gamut-mapping" "^1.0.11" + "@csstools/postcss-gradients-interpolation-method" "^4.0.20" + "@csstools/postcss-hwb-function" "^3.0.18" + "@csstools/postcss-ic-unit" "^3.0.7" + "@csstools/postcss-initial" "^1.0.1" + "@csstools/postcss-is-pseudo-class" "^4.0.8" + "@csstools/postcss-light-dark-function" "^1.0.8" + "@csstools/postcss-logical-float-and-clear" "^2.0.1" + "@csstools/postcss-logical-overflow" "^1.0.1" + "@csstools/postcss-logical-overscroll-behavior" "^1.0.1" + "@csstools/postcss-logical-resize" "^2.0.1" + "@csstools/postcss-logical-viewport-units" "^2.0.11" + "@csstools/postcss-media-minmax" "^1.1.8" + "@csstools/postcss-media-queries-aspect-ratio-number-values" "^2.0.11" + "@csstools/postcss-nested-calc" "^3.0.2" + "@csstools/postcss-normalize-display-values" "^3.0.2" + "@csstools/postcss-oklab-function" "^3.0.19" + "@csstools/postcss-progressive-custom-properties" "^3.3.0" + "@csstools/postcss-relative-color-syntax" "^2.0.19" + "@csstools/postcss-scope-pseudo-class" "^3.0.1" + "@csstools/postcss-stepped-value-functions" "^3.0.10" + "@csstools/postcss-text-decoration-shorthand" "^3.0.7" + "@csstools/postcss-trigonometric-functions" "^3.0.10" + "@csstools/postcss-unset-value" "^3.0.1" + autoprefixer "^10.4.19" + browserslist "^4.23.1" + css-blank-pseudo "^6.0.2" + css-has-pseudo "^6.0.5" + css-prefers-color-scheme "^9.0.1" + cssdb "^8.1.0" + postcss-attribute-case-insensitive "^6.0.3" + postcss-clamp "^4.1.0" + postcss-color-functional-notation "^6.0.14" + postcss-color-hex-alpha "^9.0.4" + postcss-color-rebeccapurple "^9.0.3" + postcss-custom-media "^10.0.8" + postcss-custom-properties "^13.3.12" + postcss-custom-selectors "^7.1.12" + postcss-dir-pseudo-class "^8.0.1" + postcss-double-position-gradients "^5.0.7" + postcss-focus-visible "^9.0.1" + postcss-focus-within "^8.0.1" + postcss-font-variant "^5.0.0" + postcss-gap-properties "^5.0.1" + postcss-image-set-function "^6.0.3" + postcss-lab-function "^6.0.19" + postcss-logical "^7.0.1" + postcss-nesting "^12.1.5" + postcss-opacity-percentage "^2.0.0" + postcss-overflow-shorthand "^5.0.1" + postcss-page-break "^3.0.4" + postcss-place "^9.0.1" + postcss-pseudo-class-any-link "^9.0.2" + postcss-replace-overflow-wrap "^4.0.0" + postcss-selector-not "^7.0.2" + postcss-pseudo-class-any-link@^7.1.2: version "7.1.6" resolved "https://registry.npmmirror.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz#2693b221902da772c278def85a4d9a64b6e617ab" @@ -22736,6 +24389,13 @@ postcss-pseudo-class-any-link@^7.1.2: dependencies: postcss-selector-parser "^6.0.10" +postcss-pseudo-class-any-link@^9.0.2: + version "9.0.2" + resolved "https://registry.npmmirror.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-9.0.2.tgz#e436a7db1421f8a347fff3f19951a27d4e791987" + integrity sha512-HFSsxIqQ9nA27ahyfH37cRWGk3SYyQLpk0LiWw/UGMV4VKT5YG2ONee4Pz/oFesnK0dn2AjcyequDbIjKJgB0g== + dependencies: + postcss-selector-parser "^6.0.13" + postcss-replace-overflow-wrap@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" @@ -22748,6 +24408,13 @@ postcss-selector-not@^5.0.0: dependencies: balanced-match "^1.0.0" +postcss-selector-not@^7.0.2: + version "7.0.2" + resolved "https://registry.npmmirror.com/postcss-selector-not/-/postcss-selector-not-7.0.2.tgz#f9184c7770be5dcb4abd7efa3610a15fbd2f0b31" + integrity sha512-/SSxf/90Obye49VZIfc0ls4H0P6i6V1iHv0pzZH8SdgvZOPFkF37ef1r5cyWcMflJSFJ5bfuoluTnFnBBFiuSA== + dependencies: + postcss-selector-parser "^6.0.13" + postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: version "6.0.13" resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" @@ -22756,6 +24423,22 @@ postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selecto cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.1.0: + version "6.1.2" + resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-selector-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz#41bd8b56f177c093ca49435f65731befe25d6b9c" + integrity sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-syntax@0.36.2: version "0.36.2" resolved "https://registry.npmmirror.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" @@ -22775,6 +24458,15 @@ postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.32, postcss@^8.4.7: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.29, postcss@^8.4.33: + version "8.4.49" + resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + postgres-array@~2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" @@ -23080,7 +24772,7 @@ pseudomap@^1.0.2: resolved "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== -psl@^1.1.28, psl@^1.1.33: +psl@^1.1.28: version "1.9.0" resolved "https://registry.npmmirror.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== @@ -23140,7 +24832,7 @@ punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1: resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -23189,11 +24881,6 @@ querystring-es3@^0.2.0: resolved "https://registry.npmmirror.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== -querystringify@^2.1.1: - version "2.2.0" - resolved "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" - integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -23370,14 +25057,14 @@ rc-dropdown@~4.1.0: classnames "^2.2.6" rc-util "^5.17.0" -rc-field-form@~1.27.4: - version "1.27.4" - resolved "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-1.27.4.tgz#53600714af5b28c226c70d34867a8c52ccd64d44" - integrity sha512-PQColQnZimGKArnOh8V2907+VzDCXcqtFvHgevDLtqWc/P7YASb/FqntSmdS8q3VND5SHX3Y1vgMIzY22/f/0Q== +rc-field-form@^1.34.2: + version "1.44.0" + resolved "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-1.44.0.tgz#a66548790fbcee8c5432e9f2efcd1b46b090984b" + integrity sha512-el7w87fyDUsca63Y/s8qJcq9kNkf/J5h+iTdqG5WsSHLH0e6Usl7QuYSmSVzJMgtp40mOVZIY/W/QP9zwrp1FA== dependencies: "@babel/runtime" "^7.18.0" async-validator "^4.1.0" - rc-util "^5.8.0" + rc-util "^5.32.2" rc-field-form@~1.41.0: version "1.41.0" @@ -23531,6 +25218,16 @@ rc-segmented@~2.2.2: rc-motion "^2.4.4" rc-util "^5.17.0" +rc-segmented@~2.4.1: + version "2.4.1" + resolved "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.4.1.tgz#b6bbdd6acf529c1e2ef30fb26fb3851d5966aa00" + integrity sha512-KUi+JJFdKnumV9iXlm+BJ00O4NdVBp2TEexLCk6bK1x/RH83TvYKQMzIz/7m3UTRPD08RM/8VG/JNjWgWbd4cw== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-motion "^2.4.4" + rc-util "^5.17.0" + rc-select@~14.10.0: version "14.10.0" resolved "https://registry.npmmirror.com/rc-select/-/rc-select-14.10.0.tgz#5f60e61ed7c9a83c8591616b1174a1c4ab2de0cd" @@ -23684,7 +25381,7 @@ rc-upload@~4.5.2: classnames "^2.2.5" rc-util "^5.2.0" -rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.5, rc-util@^5.24.4, rc-util@^5.25.2, rc-util@^5.27.0, rc-util@^5.28.0, rc-util@^5.30.0, rc-util@^5.31.1, rc-util@^5.32.0, rc-util@^5.32.2, rc-util@^5.34.1, rc-util@^5.35.0, rc-util@^5.36.0, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.38.1, rc-util@^5.8.0: +rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.5, rc-util@^5.24.4, rc-util@^5.25.2, rc-util@^5.27.0, rc-util@^5.28.0, rc-util@^5.30.0, rc-util@^5.31.1, rc-util@^5.32.0, rc-util@^5.32.2, rc-util@^5.34.1, rc-util@^5.35.0, rc-util@^5.36.0, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.38.1: version "5.38.1" resolved "https://registry.npmmirror.com/rc-util/-/rc-util-5.38.1.tgz#4915503b89855f5c5cd9afd4c72a7a17568777bb" integrity sha512-e4ZMs7q9XqwTuhIK7zBIVFltUtMSjphuPPQXHoHlzRzNdOwUxDejo0Zls5HYaJfRKNURcsS/ceKVULlhjBrxng== @@ -23975,11 +25672,11 @@ react-router-dom@6.3.0, react-router-dom@6.x, react-router-dom@^6.11.2, react-ro react-router "6.21.0" react-router@6.21.0, react-router@6.3.0, react-router@^6.11.2: - version "6.26.2" - resolved "https://registry.npmmirror.com/react-router/-/react-router-6.26.2.tgz#2f0a68999168954431cdc29dd36cec3b6fa44a7e" - integrity sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A== + version "6.28.0" + resolved "https://registry.npmmirror.com/react-router/-/react-router-6.28.0.tgz#29247c86d7ba901d7e5a13aa79a96723c3e59d0d" + integrity sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg== dependencies: - "@remix-run/router" "1.19.2" + "@remix-run/router" "1.21.0" react-side-effect@^2.1.0: version "2.1.2" @@ -24172,16 +25869,6 @@ readable-stream@^4.2.0: process "^0.11.10" string_decoder "^1.3.0" -readable-stream@~1.0.17, readable-stream@~1.0.27-1: - version "1.0.34" - resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readdir-glob@^1.1.2: version "1.1.3" resolved "https://registry.npmmirror.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" @@ -24250,18 +25937,6 @@ redent@^4.0.0: indent-string "^5.0.0" strip-indent "^4.0.0" -redis-errors@^1.0.0, redis-errors@^1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" - integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== - -redis-parser@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" - integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== - dependencies: - redis-errors "^1.0.0" - redis@^4.6.10, redis@^4.6.7: version "4.6.11" resolved "https://registry.npmmirror.com/redis/-/redis-4.6.11.tgz#fad85e104545228f212259fd557c3e4f8eafcd3d" @@ -24274,13 +25949,6 @@ redis@^4.6.10, redis@^4.6.7: "@redis/search" "1.1.6" "@redis/time-series" "1.0.5" -redlock@^5.0.0-beta.2: - version "5.0.0-beta.2" - resolved "https://registry.npmmirror.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44" - integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw== - dependencies: - node-abort-controller "^3.0.1" - redux@^4.0.0, redux@^4.0.4: version "4.2.1" resolved "https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" @@ -24312,6 +25980,13 @@ regenerate-unicode-properties@10.1.1, regenerate-unicode-properties@^10.1.0: dependencies: regenerate "^1.4.2" +regenerate-unicode-properties@^10.2.0: + version "10.2.0" + resolved "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" + integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== + dependencies: + regenerate "^1.4.2" + regenerate@^1.4.2: version "1.4.2" resolved "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" @@ -24347,7 +26022,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: +regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== @@ -24383,6 +26058,18 @@ regexpu-core@^5.3.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" +regexpu-core@^6.1.1: + version "6.1.1" + resolved "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.1.1.tgz#b469b245594cb2d088ceebc6369dceb8c00becac" + integrity sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.0" + regjsgen "^0.8.0" + regjsparser "^0.11.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + registry-auth-token@3.3.2: version "3.3.2" resolved "https://registry.npmmirror.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" @@ -24420,6 +26107,18 @@ registry-url@^5.0.0: dependencies: rc "^1.2.8" +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.11.0: + version "0.11.2" + resolved "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.11.2.tgz#7404ad42be00226d72bcf1f003f1f441861913d8" + integrity sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA== + dependencies: + jsesc "~3.0.2" + regjsparser@^0.9.1: version "0.9.1" resolved "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" @@ -24660,11 +26359,6 @@ requireindex@^1.2.0: resolved "https://registry.npmmirror.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" @@ -24744,7 +26438,7 @@ resolve@^1.0.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1 path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^2.0.0-next.4: +resolve@^2.0.0-next.4, resolve@^2.0.0-next.5: version "2.0.0-next.5" resolved "https://registry.npmmirror.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== @@ -24899,6 +26593,11 @@ rollup@^4.19.0, rollup@^4.2.0: "@rollup/rollup-win32-x64-msvc" "4.20.0" fsevents "~2.3.2" +rrweb-cssom@^0.7.1: + version "0.7.1" + resolved "https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" + integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== + run-applescript@^5.0.0: version "5.0.0" resolved "https://registry.npmmirror.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" @@ -25062,10 +26761,10 @@ sax@~1.2.4: resolved "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== dependencies: xmlchars "^2.2.0" @@ -25450,6 +27149,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + siginfo@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" @@ -25525,6 +27234,14 @@ smart-buffer@^4.2.0: resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmmirror.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.npmmirror.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -25654,6 +27371,11 @@ sort-package-json@2.4.1: resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.0.1, source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.npmmirror.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -25921,11 +27643,6 @@ staged-components@^1.1.3: resolved "https://registry.npmmirror.com/staged-components/-/staged-components-1.1.3.tgz#bb5a396df2d9b48fbc31841a59f53437ed8b8ac6" integrity sha512-9EIswzDqjwlEu+ymkV09TTlJfzSbKgEnNteUnZSTxkpMgr5Wx2CzzA9WcMFWBNCldqVPsHVnRGGrApduq2Se5A== -standard-as-callback@^2.1.0: - version "2.1.0" - resolved "https://registry.npmmirror.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" - integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== - static-extend@^0.1.1: version "0.1.2" resolved "https://registry.npmmirror.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -26099,20 +27816,31 @@ string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.matchall@^4.0.8: - version "4.0.10" - resolved "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" - integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== +string.prototype.matchall@^4.0.11, string.prototype.matchall@^4.0.8: + version "4.0.11" + resolved "https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" + integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" + gopd "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.5" - regexp.prototype.flags "^1.5.0" - set-function-name "^2.0.0" - side-channel "^1.0.4" + internal-slot "^1.0.7" + regexp.prototype.flags "^1.5.2" + set-function-name "^2.0.2" + side-channel "^1.0.6" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" string.prototype.trim@^1.2.8, string.prototype.trim@~1.2.8: version "1.2.8" @@ -26311,6 +28039,11 @@ strong-log-transformer@^2.1.0: minimist "^1.2.0" through "^2.3.4" +style-loader@^3.3.3: + version "3.3.4" + resolved "https://registry.npmmirror.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7" + integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w== + style-to-object@^0.4.1: version "0.4.4" resolved "https://registry.npmmirror.com/style-to-object/-/style-to-object-0.4.4.tgz#266e3dfd56391a7eefb7770423612d043c3f33ec" @@ -26351,11 +28084,16 @@ stylis@4.2.0: resolved "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== -stylis@^4.0.13, stylis@^4.1.2: +stylis@^4.1.2: version "4.3.0" resolved "https://registry.npmmirror.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== +stylis@^4.3.3: + version "4.3.4" + resolved "https://registry.npmmirror.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4" + integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now== + sucrase@^3.35.0: version "3.35.0" resolved "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" @@ -26485,6 +28223,19 @@ svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" +svgo@^3.0.2: + version "3.3.2" + resolved "https://registry.npmmirror.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + svgson@^4.1.0: version "4.1.0" resolved "https://registry.npmmirror.com/svgson/-/svgson-4.1.0.tgz#eb70dac8d0075c61e5bfd45411a56014e2d3610e" @@ -26807,14 +28558,6 @@ through2@^4.0.0: dependencies: readable-stream "3" -through2@~0.4.1: - version "0.4.2" - resolved "https://registry.npmmirror.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b" - integrity sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ== - dependencies: - readable-stream "~1.0.17" - xtend "~2.1.1" - through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3: version "2.3.8" resolved "https://registry.npmmirror.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -26872,6 +28615,18 @@ titleize@^3.0.0: resolved "https://registry.npmmirror.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== +tldts-core@^6.1.60: + version "6.1.60" + resolved "https://registry.npmmirror.com/tldts-core/-/tldts-core-6.1.60.tgz#b3f0a2106e575e972bfca98880758c85c7557d02" + integrity sha512-XHjoxak8SFQnHnmYHb3PcnW5TZ+9ErLZemZei3azuIRhQLw4IExsVbL3VZJdHcLeNaXq6NqawgpDPpjBOg4B5g== + +tldts@^6.1.32: + version "6.1.60" + resolved "https://registry.npmmirror.com/tldts/-/tldts-6.1.60.tgz#5011770e6946fd2edec582ab4686247c66c97e8a" + integrity sha512-TYVHm7G9NCnhgqOsFalbX6MG1Po5F4efF+tLfoeiOGQq48Oqgwcgz8upY2R1BHWa4aDrj28RYx0dkYJ63qCFMg== + dependencies: + tldts-core "^6.1.60" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.npmmirror.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -26976,15 +28731,12 @@ toposort@^2.0.2: resolved "https://registry.npmmirror.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== -tough-cookie@^4.0.0: - version "4.1.3" - resolved "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" - integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== +tough-cookie@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af" + integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q== dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" + tldts "^6.1.32" tough-cookie@~2.5.0: version "2.5.0" @@ -27008,6 +28760,13 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== + dependencies: + punycode "^2.3.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -27767,11 +29526,6 @@ universalify@^0.1.0: resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -universalify@^0.2.0: - version "0.2.0" - resolved "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" - integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== - universalify@^2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -27839,6 +29593,14 @@ update-browserslist-db@^1.1.0: escalade "^3.1.2" picocolors "^1.0.1" +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + update-check@1.5.2: version "1.5.2" resolved "https://registry.npmmirror.com/update-check/-/update-check-1.5.2.tgz#2fe09f725c543440b3d7dabe8971f2d5caaedc28" @@ -27914,14 +29676,6 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.5.3: - version "1.5.10" - resolved "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" - integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== - dependencies: - querystringify "^2.1.1" - requires-port "^1.0.0" - url@^0.11.0, url@^0.11.1: version "0.11.3" resolved "https://registry.npmmirror.com/url/-/url-0.11.3.tgz#6f495f4b935de40ce4a0a52faee8954244f3d3ad" @@ -27978,7 +29732,7 @@ use-memo-one@^1.1.1: resolved "https://registry.npmmirror.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== -use-merge-value@^1: +use-merge-value@^1.2.0: version "1.2.0" resolved "https://registry.npmmirror.com/use-merge-value/-/use-merge-value-1.2.0.tgz#45410846c23e490f404c9cbd17d67db9c8c0efcd" integrity sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw== @@ -28260,9 +30014,9 @@ vite-node@1.5.2: vite "^5.0.0" vite-plugin-css-injected-by-js@^3.2.1: - version "3.3.0" - resolved "https://registry.npmmirror.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.3.0.tgz#c19480a9e42a95c5bced976a9dde1446f9bd91ff" - integrity sha512-xG+jyHNCmUqi/TXp6q88wTJGeAOrNLSyUUTp4qEQ9QZLGcHWQQsCsSSKa59rPMQr8sOzfzmWDd8enGqfH/dBew== + version "3.5.2" + resolved "https://registry.npmmirror.com/vite-plugin-css-injected-by-js/-/vite-plugin-css-injected-by-js-3.5.2.tgz#1f75d16ad5c05b6b49bf18018099a189ec2e46ad" + integrity sha512-2MpU/Y+SCZyWUB6ua3HbJCrgnF0KACAsmzOQt1UvRVJCGF6S8xdA3ZUhWcWdM9ivG4I5az8PnQmwwrkC2CAQrQ== vite-plugin-lib-inject-css@1.2.0: version "1.2.0" @@ -28352,19 +30106,12 @@ void-elements@3.1.0: resolved "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.npmmirror.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== - dependencies: - xml-name-validator "^3.0.0" + xml-name-validator "^5.0.0" walker@^1.0.8: version "1.0.8" @@ -28419,27 +30166,35 @@ webidl-conversions@^4.0.2: resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - webidl-conversions@^6.1.0: version "6.1.0" resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^14.0.0: + version "14.0.0" + resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" + integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== + dependencies: + tr46 "^5.0.0" + webidl-conversions "^7.0.0" whatwg-url@^5.0.0: version "5.0.0" @@ -28458,7 +30213,7 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -whatwg-url@^8.0.0, whatwg-url@^8.4.0, whatwg-url@^8.5.0: +whatwg-url@^8.4.0: version "8.7.0" resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== @@ -28756,15 +30511,15 @@ write-pkg@^4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@^7.0.0, ws@^7.4.6: +ws@^7.0.0: version "7.5.9" resolved "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.13.0: - version "8.15.1" - resolved "https://registry.npmmirror.com/ws/-/ws-8.15.1.tgz#271ba33a45ca0cc477940f7f200cd7fba7ee1997" - integrity sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ== +ws@^8.13.0, ws@^8.18.0: + version "8.18.0" + resolved "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== ws@~7.4.0: version "7.4.6" @@ -28817,10 +30572,10 @@ xml-lexer@^0.2.2: dependencies: eventemitter3 "^2.0.0" -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== xml-reader@2.4.3: version "2.4.3" @@ -28889,13 +30644,6 @@ xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xtend@~2.1.1: - version "2.1.2" - resolved "https://registry.npmmirror.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" - integrity sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ== - dependencies: - object-keys "~0.4.0" - xxhashjs@^0.2.2: version "0.2.2" resolved "https://registry.npmmirror.com/xxhashjs/-/xxhashjs-0.2.2.tgz#8a6251567621a1c46a5ae204da0249c7f8caa9d8"
{designable ? props.children : null}; }; -const BodyRowComponent = (props) => { +const BodyRowComponent = (props: { + rowIndex: number; + onClick: (e: any) => void; + style: React.CSSProperties; + className: string; +}) => { return ; }; +const InternalBodyCellComponent = (props) => { + const { token } = useToken(); + const inView = useContext(InViewContext); + const isIndex = props.className?.includes('selection-column'); + const { record, schema, rowIndex, isSubTable, ...others } = props; + const { valueMap } = useSatisfiedActionValues({ formValues: record, category: 'style', schema }); + const style = useMemo(() => Object.assign({ ...props.style }, valueMap), [props.style, valueMap]); + const skeletonStyle = { + height: '1em', + backgroundColor: 'rgba(0, 0, 0, 0.06)', + borderRadius: `${token.borderRadiusSM}px`, + }; + + return ( + + {/* Lazy rendering cannot be used in sub-tables. */} + {isSubTable || inView || isIndex ? props.children :
} +
+ {designable ? props.children : {props.children}} +
- {props.children} - - {/* 子表格中不能使用懒渲染。详见:https://nocobase.height.app/T-4889/description */} - {others.isSubTable || inView || isIndex ? props.children : } -