mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 06:46:38 +00:00
Merge branch 'next' into F-827
This commit is contained in:
commit
c0caf63b97
34
.github/workflows/build-docker-image.yml
vendored
34
.github/workflows/build-docker-image.yml
vendored
@ -62,28 +62,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
|
||||
|
10
.github/workflows/changelog-and-release.yml
vendored
10
.github/workflows/changelog-and-release.yml
vendored
@ -16,7 +16,7 @@ on:
|
||||
default: beta
|
||||
push:
|
||||
tags:
|
||||
- 'v*-beta'
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
write-changelog-and-release:
|
||||
@ -24,12 +24,15 @@ jobs:
|
||||
steps:
|
||||
- name: Get info
|
||||
id: get-info
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ inputs.version }}" == "alpha" ]]; then
|
||||
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
|
||||
@ -81,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: ${{ 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 .
|
||||
|
2
.github/workflows/manual-build-pro-image.yml
vendored
2
.github/workflows/manual-build-pro-image.yml
vendored
@ -1,7 +1,7 @@
|
||||
name: Manual build pro image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.pr_number }}-${{ github.event.inputs.nocobase_pr_number }}
|
||||
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 }}
|
||||
|
@ -1,10 +1,10 @@
|
||||
name: Build pro plugin docker image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.pro_plugin }}-${{ github.events.inputs.pr_number }}-${{ github.events.inputs.nocobase_pr_number }}
|
||||
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 }}-${{ github.event.inputs.pro_plugin }}-${{ github.events.inputs.pr_number }}-${{ github.events.inputs.nocobase_pr_number }}
|
||||
run-name: Build pro plugin image ${{ github.ref }}-${{ inputs.pro_plugin }}-${{ inputs.pr_number }}-${{ inputs.nocobase_pr_number }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
86
.github/workflows/manual-release-next.yml
vendored
Normal file
86
.github/workflows/manual-release-next.yml
vendored
Normal file
@ -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
|
7
.github/workflows/manual-release.yml
vendored
7
.github/workflows/manual-release.yml
vendored
@ -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
|
||||
|
160
.github/workflows/release-next.yml
vendored
160
.github/workflows/release-next.yml
vendored
@ -1,160 +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
|
||||
steps:
|
||||
- name: Set Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- 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
|
||||
ref: next
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
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 }}
|
51
.github/workflows/release.yml
vendored
51
.github/workflows/release.yml
vendored
@ -5,24 +5,38 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*-beta'
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
publish-npm:
|
||||
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,19 @@ 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
|
||||
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
|
||||
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 }}
|
||||
|
38
CHANGELOG.md
38
CHANGELOG.md
@ -5,6 +5,44 @@ 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.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
|
||||
|
@ -5,6 +5,44 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||
|
||||
## [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
|
||||
|
||||
### 🐛 修复
|
||||
|
@ -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
|
||||
|
@ -7,9 +7,6 @@ https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd1
|
||||
|
||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## 加入我们
|
||||
我们正在招聘远程 **全栈开发工程师** 、 **测试工程师** 、 **技术培训与文档专家**。 欢迎对 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 支持三种安装方式:
|
||||
- <a target="_blank" href="https://docs-cn.nocobase.com/welcome/getting-started/installation/git-clone">Git 源码安装</a>
|
||||
|
||||
如果你想体验最新未发布版本,或者想参与贡献,需要在源码上进行修改、调试,建议选择这种安装方式,对开发技术水平要求较高,如果代码有更新,可以走 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)
|
||||
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
"npmClientArgs": [
|
||||
"--ignore-engines"
|
||||
],
|
||||
"command": {
|
||||
"version": {
|
||||
"forcePublish": true,
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/acl",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@nocobase/utils": "1.4.0-alpha.2",
|
||||
"minimatch": "^5.1.1"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@nocobase/actions",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@nocobase/database": "1.4.0-alpha.2",
|
||||
"@nocobase/resourcer": "1.4.0-alpha.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -37,6 +37,7 @@ export default defineConfig({
|
||||
`,
|
||||
},
|
||||
],
|
||||
cacheDirectoryPath: process.env.APP_CLIENT_CACHE_DIR || `node_modules/.cache`,
|
||||
outputPath: path.resolve(__dirname, '../dist/client'),
|
||||
hash: true,
|
||||
alias: {
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@nocobase/app",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@nocobase/preset-nocobase": "1.4.0-alpha.2",
|
||||
"@nocobase/server": "1.4.0-alpha.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "1.4.0-alpha"
|
||||
"@nocobase/client": "1.4.0-alpha.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/auth",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@nocobase/cache": "1.4.0-alpha.2",
|
||||
"@nocobase/database": "1.4.0-alpha.2",
|
||||
"@nocobase/resourcer": "1.4.0-alpha.2",
|
||||
"@nocobase/utils": "1.4.0-alpha.2",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/build",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"description": "Library build tool based on rollup.",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
|
2
packages/core/cache/package.json
vendored
2
packages/core/cache/package.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cache",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cli",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@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.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -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();
|
||||
|
@ -350,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 (
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/client",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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,12 +27,12 @@
|
||||
"@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.2",
|
||||
"@nocobase/sdk": "1.4.0-alpha.2",
|
||||
"@nocobase/utils": "1.4.0-alpha.2",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.12.8",
|
||||
"antd-style": "3.4.5",
|
||||
"antd-style": "3.7.1",
|
||||
"axios": "^1.7.0",
|
||||
"bignumber.js": "^9.1.2",
|
||||
"classnames": "^2.3.1",
|
||||
|
@ -345,7 +345,7 @@ export const useACLFieldWhitelist = () => {
|
||||
return {
|
||||
whitelist,
|
||||
schemaInWhitelist: useCallback(
|
||||
(fieldSchema: Schema, isSkip?) => {
|
||||
(fieldSchema: Schema | any, isSkip?) => {
|
||||
if (isSkip) {
|
||||
return true;
|
||||
}
|
||||
@ -359,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],
|
||||
),
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
@ -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<SchemaInitializerItemGroupProps> = (
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
export const SchemaInitializerItemGroupInternal = () => {
|
||||
const itemConfig = useSchemaInitializerItem<SchemaInitializerItemGroupProps>();
|
||||
return <SchemaInitializerItemGroup {...itemConfig} />;
|
||||
const itemConfig: any = useSchemaInitializerItem<SchemaInitializerItemGroupProps>();
|
||||
|
||||
const searchedChildren = useMenuSearch(itemConfig);
|
||||
if (itemConfig.name !== 'displayFields') {
|
||||
return <SchemaInitializerItemGroup {...itemConfig} />;
|
||||
}
|
||||
/* eslint-disable react/no-children-prop */
|
||||
return <SchemaInitializerItemGroup {...itemConfig} children={searchedChildren} />;
|
||||
};
|
||||
|
@ -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<string>(outValue);
|
||||
const inputRef = useRef<any>('');
|
||||
|
||||
// 生成唯一的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<boolean>(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!compositionRef.current) {
|
||||
onChange(e.target.value);
|
||||
setValue(e.target.value);
|
||||
}
|
||||
};
|
||||
const Composition = (e: React.CompositionEvent<HTMLInputElement> | any) => {
|
||||
if (e.type === 'compositionend') {
|
||||
compositionRef.current = false;
|
||||
handleChange(e);
|
||||
} else {
|
||||
compositionRef.current = true;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
allowClear
|
||||
style={{ padding: '0 4px 6px 16px', boxShadow: 'none' }}
|
||||
bordered={false}
|
||||
placeholder={t('Search')}
|
||||
defaultValue={value}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onChange={handleChange}
|
||||
onCompositionStart={Composition}
|
||||
onCompositionEnd={Composition}
|
||||
onCompositionUpdate={Composition}
|
||||
/>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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<MenuProps['items']>(() => {
|
||||
const res = [];
|
||||
if (!hideSearch && (items.length > 10 || searchValue)) {
|
||||
res.push({
|
||||
key: `search-${uid()}`,
|
||||
Component: () => (
|
||||
<SearchFields
|
||||
name={name}
|
||||
value={searchValue}
|
||||
onChange={(val: string) => {
|
||||
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: () => (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
</div>
|
||||
),
|
||||
...(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;
|
||||
});
|
||||
};
|
@ -101,6 +101,7 @@ export function useGetSchemaInitializerMenuItems(onClick?: (args: any) => void)
|
||||
onClick: handleClick,
|
||||
}
|
||||
: {
|
||||
style: item.style,
|
||||
key,
|
||||
label,
|
||||
onClick: handleClick,
|
||||
|
@ -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<string>();
|
||||
const appends = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
const appends = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
const appends = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
const appends = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
const appends = new Set<string>();
|
||||
|
||||
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<string>();
|
||||
const appends = new Set<string>();
|
||||
|
||||
getAppends({
|
||||
schema,
|
||||
prefix: '',
|
||||
updateAssociationValues,
|
||||
appends,
|
||||
getCollectionJoinField: mockGetCollectionJoinField,
|
||||
getCollection: mockGetCollection,
|
||||
dataSource: 'main',
|
||||
});
|
||||
|
||||
expect(Array.from(appends)).toEqual([]);
|
||||
expect(Array.from(updateAssociationValues)).toEqual([]);
|
||||
});
|
||||
});
|
@ -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';
|
||||
@ -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<string>;
|
||||
appends: Set<string>;
|
||||
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] };
|
||||
};
|
||||
|
||||
|
@ -28,16 +28,7 @@ export const CollectionManagerProvider_deprecated: React.FC<CollectionManagerOpt
|
||||
);
|
||||
};
|
||||
|
||||
const coptions = {
|
||||
url: 'collectionCategories:list',
|
||||
params: {
|
||||
paginate: false,
|
||||
sort: ['sort'],
|
||||
},
|
||||
};
|
||||
|
||||
export const RemoteCollectionManagerProvider = (props: any) => {
|
||||
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 (
|
||||
<CollectionCategoriesProvider service={result} refreshCategory={refreshCategory}>
|
||||
<CollectionManagerProvider_deprecated {...props}></CollectionManagerProvider_deprecated>
|
||||
</CollectionCategoriesProvider>
|
||||
);
|
||||
return <CollectionManagerProvider_deprecated {...props}></CollectionManagerProvider_deprecated>;
|
||||
};
|
||||
|
||||
export const CollectionCategoriesProvider = (props) => {
|
||||
|
@ -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' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -1017,5 +1017,13 @@
|
||||
"Left": "左",
|
||||
"Center": "居中",
|
||||
"Right": "右",
|
||||
"Divider line color": "分割线颜色"
|
||||
"Divider line color": "分割线颜色",
|
||||
"Label align": "字段标题对齐方式",
|
||||
"Label width": "字段标题宽度",
|
||||
"When the Label exceeds the width": "字段标题超出宽度时",
|
||||
"Line break": "换行",
|
||||
"Ellipsis": "省略",
|
||||
"Set block layout": "设置区块布局",
|
||||
"Add & Update": "添加 & 更新",
|
||||
"Table size":"表格大小"
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export const PopupActionInitializer = (props) => {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'popup:common:addBlock',
|
||||
'x-initializer': props?.['x-initializer'] || 'popup:common:addBlock',
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
|
@ -16,13 +16,14 @@ import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockP
|
||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||
import { useCollection_deprecated, useSortFields } from '../../../../collection-manager';
|
||||
import { removeNullCondition, useDesignable } from '../../../../schema-component';
|
||||
import { SchemaSettingsLinkageRules, schemaSettingsLabelLayout } from '../../../../schema-settings';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||
import { SchemaSettingsDataScope } from '../../../../schema-settings/SchemaSettingsDataScope';
|
||||
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,7 +213,10 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
|
@ -11,14 +11,12 @@ import { useFieldSchema } from '@formily/react';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
||||
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||
import {
|
||||
SchemaSettingsFormItemTemplate,
|
||||
SchemaSettingsLinkageRules,
|
||||
schemaSettingsLabelLayout,
|
||||
} from '../../../../schema-settings';
|
||||
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
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',
|
||||
@ -56,7 +54,10 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
};
|
||||
},
|
||||
},
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
|
@ -16,11 +16,11 @@ import {
|
||||
SchemaSettingsDataTemplates,
|
||||
SchemaSettingsFormItemTemplate,
|
||||
SchemaSettingsLinkageRules,
|
||||
schemaSettingsLabelLayout,
|
||||
} from '../../../../schema-settings';
|
||||
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',
|
||||
@ -77,7 +77,10 @@ export const createFormBlockSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
{
|
||||
name: 'divider2',
|
||||
type: 'divider',
|
||||
|
@ -16,11 +16,11 @@ import {
|
||||
SchemaSettingsDataTemplates,
|
||||
SchemaSettingsFormItemTemplate,
|
||||
SchemaSettingsLinkageRules,
|
||||
schemaSettingsLabelLayout,
|
||||
} from '../../../../schema-settings';
|
||||
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',
|
||||
@ -77,7 +77,10 @@ export const editFormBlockSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
{
|
||||
name: 'divider2',
|
||||
type: 'divider',
|
||||
|
@ -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<Field>();
|
||||
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();
|
||||
},
|
||||
};
|
||||
|
@ -7,11 +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 data = useCollectionRecordData();
|
||||
const { valueMap: style } = useSatisfiedActionValues({ category: 'style', formValues: data });
|
||||
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 };
|
||||
}
|
||||
|
@ -23,7 +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 { schemaSettingsLabelLayout } from '../../../../schema-settings';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
|
||||
export const gridCardBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:gridCard',
|
||||
@ -222,7 +222,10 @@ export const gridCardBlockSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
|
@ -21,7 +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 { schemaSettingsLabelLayout } from '../../../../schema-settings';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
|
||||
export const listBlockSettings = new SchemaSettings({
|
||||
name: 'blockSettings:list',
|
||||
@ -225,7 +225,10 @@ export const listBlockSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
|
@ -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();
|
||||
|
@ -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')}}",
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -13,15 +13,12 @@ import { SchemaSettings } from '../../../../application/schema-settings/SchemaSe
|
||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||
import { FilterBlockType } from '../../../../filter-provider';
|
||||
import {
|
||||
SchemaSettingsFormItemTemplate,
|
||||
SchemaSettingsLinkageRules,
|
||||
schemaSettingsLabelLayout,
|
||||
} from '../../../../schema-settings';
|
||||
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
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',
|
||||
@ -71,7 +68,10 @@ export const filterFormBlockSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
|
@ -17,7 +17,7 @@ import { SchemaSettings } from '../../../../application/schema-settings/SchemaSe
|
||||
import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../../collection-manager';
|
||||
import { useFieldComponentName } from '../../../../common/useFieldComponentName';
|
||||
import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem';
|
||||
import { EditOperator, useDesignable, useValidateSchema } from '../../../../schema-component';
|
||||
import { EditOperator, useDesignable, useValidateSchema, useCompile } from '../../../../schema-component';
|
||||
import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue';
|
||||
|
||||
const fieldComponentNameMap = (name: string) => {
|
||||
@ -49,6 +49,7 @@ export const filterFormItemFieldSettings = new SchemaSettings({
|
||||
const { t } = useTranslation();
|
||||
const { dn } = useDesignable();
|
||||
const field = useField<Field>();
|
||||
const compile = useCompile();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { getCollectionJoinField } = useCollectionManager_deprecated();
|
||||
const { getField } = useCollection_deprecated();
|
||||
@ -72,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();
|
||||
},
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
useIsFormReadPretty,
|
||||
} from '../../../../schema-component/antd/form-item/FormItem.Settings';
|
||||
import { linkageRules, setDefaultSortingRules } from '../SubTable/subTablePopoverComponentFieldSettings';
|
||||
import { schemaSettingsLabelLayout } from '../../../../schema-settings';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
|
||||
const allowMultiple: any = {
|
||||
name: 'allowMultiple',
|
||||
@ -144,6 +144,9 @@ export const subformComponentFieldSettings = new SchemaSettings({
|
||||
},
|
||||
setDefaultSortingRules,
|
||||
linkageRules,
|
||||
schemaSettingsLabelLayout,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -20,7 +20,7 @@ import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Tabl
|
||||
import { titleField } from '../Select/selectComponentFieldSettings';
|
||||
|
||||
import { linkageRules } from '../SubTable/subTablePopoverComponentFieldSettings';
|
||||
import { schemaSettingsLabelLayout } from '../../../../schema-settings';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
|
||||
const allowMultiple: any = {
|
||||
name: 'allowMultiple',
|
||||
@ -106,5 +106,14 @@ const fieldComponent: any = {
|
||||
|
||||
export const subformPopoverComponentFieldSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:component:PopoverNester',
|
||||
items: [fieldComponent, allowMultiple, titleField, linkageRules, schemaSettingsLabelLayout],
|
||||
items: [
|
||||
fieldComponent,
|
||||
allowMultiple,
|
||||
titleField,
|
||||
linkageRules,
|
||||
{
|
||||
name: 'setBlockLayout',
|
||||
Component: SchemaSettingsLayoutItem,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -27,7 +27,7 @@ export const PluginAddModal: FC<IPluginFormProps> = ({ onClose, isShow }) => {
|
||||
const [type, setType] = useState<'npm' | 'upload' | 'url'>('npm');
|
||||
|
||||
return (
|
||||
<Modal onCancel={() => onClose()} footer={null} destroyOnClose title={t('Add & update')} width={580} open={isShow}>
|
||||
<Modal onCancel={() => onClose()} footer={null} destroyOnClose title={t('Add & Update')} width={580} open={isShow}>
|
||||
{/* <label style={{ fontWeight: 'bold' }}>{t('Source')}:</label> */}
|
||||
<div style={{ marginTop: theme.marginLG, marginBottom: theme.marginLG }}>
|
||||
<Radio.Group optionType="button" defaultValue={type} onChange={(e) => setType(e.target.value)}>
|
||||
|
@ -154,6 +154,7 @@ export const Action: ComposedAction = withDynamicSchemaProps(
|
||||
setSubmitted={setSubmitted}
|
||||
getAriaLabel={getAriaLabel}
|
||||
parentRecordData={parentRecordData}
|
||||
actionCallback={actionCallback}
|
||||
{...others}
|
||||
/>
|
||||
);
|
||||
|
@ -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';
|
||||
|
@ -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,14 +46,25 @@ export const InternalNester = observer(
|
||||
const { options: collectionField } = useAssociationFieldContext();
|
||||
const showTitle = fieldSchema['x-decorator-props']?.showTitle ?? true;
|
||||
const { actionName } = useACLActionParamsContext();
|
||||
const { layout = 'vertical' } = fieldSchema?.['x-component-props'] || {};
|
||||
const { token } = theme.useToken();
|
||||
const {
|
||||
layout = 'vertical',
|
||||
labelAlign = 'left',
|
||||
labelWidth = 120,
|
||||
labelWrap = true,
|
||||
} = fieldSchema?.['x-component-props'] || {};
|
||||
useEffect(() => {
|
||||
insertNester(schema.Nester);
|
||||
}, []);
|
||||
return (
|
||||
<CollectionProvider_deprecated name={collectionField.target}>
|
||||
<ACLCollectionProvider actionPath={`${collectionField.target}:${actionName || 'view'}`}>
|
||||
<FormLayout layout={layout}>
|
||||
<FormLayout
|
||||
layout={layout}
|
||||
labelAlign={labelAlign}
|
||||
labelWidth={layout === 'horizontal' ? labelWidth : null}
|
||||
labelWrap={labelWrap}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
InternalNesterCss,
|
||||
@ -62,6 +74,12 @@ export const InternalNester = observer(
|
||||
css`
|
||||
.nb-grid-container {
|
||||
height: 100% !important;
|
||||
.ant-formily-item-label {
|
||||
line-height: ${token.controlHeight}px;
|
||||
}
|
||||
.ant-formily-item-label label {
|
||||
white-space: ${labelWrap ? 'break-all' : 'nowrap'};
|
||||
}
|
||||
}
|
||||
`,
|
||||
)}
|
||||
|
@ -165,7 +165,7 @@ export const SubTable: any = observer(
|
||||
};
|
||||
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;
|
||||
};
|
||||
@ -235,6 +235,7 @@ export const SubTable: any = observer(
|
||||
// 计算总页数,并跳转到最后一页
|
||||
const totalPages = Math.ceil(field.value.length / (field.componentProps?.pageSize || 10));
|
||||
setCurrentPage(totalPages);
|
||||
return field.onInput(field.value);
|
||||
}}
|
||||
>
|
||||
{t('Add new')}
|
||||
|
@ -45,7 +45,7 @@ describe('CollectionSelect', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
@ -54,7 +54,7 @@ describe('CollectionSelect', () => {
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-wwtqkl"
|
||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
||||
>
|
||||
<div
|
||||
class="ant-formily-item-label"
|
||||
@ -84,7 +84,7 @@ describe('CollectionSelect', () => {
|
||||
class="ant-formily-item-control-content-component"
|
||||
>
|
||||
<div
|
||||
class="ant-select css-dev-only-do-not-override-wwtqkl ant-select-focused ant-select-single ant-select-show-arrow ant-select-show-search"
|
||||
class="ant-select css-dev-only-do-not-override-11aiz3o ant-select-focused ant-select-single ant-select-show-arrow ant-select-show-search"
|
||||
data-testid="select-collection"
|
||||
role="button"
|
||||
>
|
||||
@ -182,7 +182,7 @@ describe('CollectionSelect', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
@ -191,7 +191,7 @@ describe('CollectionSelect', () => {
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-wwtqkl"
|
||||
class="css-1yh5po ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
||||
>
|
||||
<div
|
||||
class="ant-formily-item-label"
|
||||
@ -222,7 +222,7 @@ describe('CollectionSelect', () => {
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="ant-tag css-dev-only-do-not-override-wwtqkl"
|
||||
class="ant-tag css-dev-only-do-not-override-11aiz3o"
|
||||
>
|
||||
Users
|
||||
</span>
|
||||
|
@ -26,7 +26,7 @@ describe('ColorPicker', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
@ -35,7 +35,7 @@ describe('ColorPicker', () => {
|
||||
style="display: inline-block;"
|
||||
>
|
||||
<div
|
||||
class="ant-color-picker-trigger css-dev-only-do-not-override-wwtqkl"
|
||||
class="ant-color-picker-trigger css-dev-only-do-not-override-11aiz3o"
|
||||
>
|
||||
<div
|
||||
class="ant-color-picker-color-block"
|
||||
@ -90,7 +90,7 @@ describe('ColorPicker', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
@ -99,7 +99,7 @@ describe('ColorPicker', () => {
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="ant-color-picker-trigger ant-color-picker-sm css-dev-only-do-not-override-wwtqkl ant-color-picker-trigger-disabled"
|
||||
class="ant-color-picker-trigger ant-color-picker-sm css-dev-only-do-not-override-11aiz3o ant-color-picker-trigger-disabled"
|
||||
>
|
||||
<div
|
||||
class="ant-color-picker-color-block"
|
||||
|
@ -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();
|
||||
}}
|
||||
/>
|
||||
|
@ -40,11 +40,22 @@ const FormComponent: React.FC<FormProps> = (props) => {
|
||||
const f = useAttach(form.createVoidField({ ...field.props, basePath: '' }));
|
||||
const height = useFormBlockHeight();
|
||||
const { token } = theme.useToken();
|
||||
const { layout = 'vertical' } = cardItemSchema?.['x-component-props'] || {};
|
||||
const {
|
||||
layout = 'vertical',
|
||||
labelAlign = 'left',
|
||||
labelWidth = 120,
|
||||
labelWrap = true,
|
||||
} = cardItemSchema?.['x-component-props'] || {};
|
||||
return (
|
||||
<FieldContext.Provider value={undefined}>
|
||||
<FormContext.Provider value={form}>
|
||||
<FormLayout layout={layout} {...others}>
|
||||
<FormLayout
|
||||
layout={layout}
|
||||
{...others}
|
||||
labelAlign={labelAlign}
|
||||
labelWidth={layout === 'horizontal' ? labelWidth : null}
|
||||
labelWrap={labelWrap}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
.nb-grid-container {
|
||||
|
@ -162,7 +162,12 @@ const InternalGridCard = (props: GridCardProps) => {
|
||||
onChange: onPaginationChange,
|
||||
};
|
||||
const cardItemSchema = getCardItemSchema?.(fieldSchema);
|
||||
const { layout = 'vertical' } = cardItemSchema?.['x-component-props'] || {};
|
||||
const {
|
||||
layout = 'vertical',
|
||||
labelAlign = 'left',
|
||||
labelWidth = 120,
|
||||
labelWrap = true,
|
||||
} = cardItemSchema?.['x-component-props'] || {};
|
||||
|
||||
return (
|
||||
<SchemaComponentOptions
|
||||
@ -187,7 +192,12 @@ const InternalGridCard = (props: GridCardProps) => {
|
||||
`,
|
||||
)}
|
||||
>
|
||||
<FormLayout layout={layout}>
|
||||
<FormLayout
|
||||
layout={layout}
|
||||
labelAlign={labelAlign}
|
||||
labelWidth={layout === 'horizontal' ? labelWidth : null}
|
||||
labelWrap={labelWrap}
|
||||
>
|
||||
<AntdList
|
||||
pagination={
|
||||
!meta || meta.count <= meta.pageSize
|
||||
|
@ -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: {
|
||||
|
@ -67,7 +67,12 @@ const InternalList = (props) => {
|
||||
[run, params],
|
||||
);
|
||||
const cardItemSchema = getCardItemSchema?.(fieldSchema);
|
||||
const { layout = 'vertical' } = cardItemSchema?.['x-component-props'] || {};
|
||||
const {
|
||||
layout = 'vertical',
|
||||
labelAlign = 'left',
|
||||
labelWidth = 120,
|
||||
labelWrap = true,
|
||||
} = cardItemSchema?.['x-component-props'] || {};
|
||||
const usePagination = () => {
|
||||
if (!count) {
|
||||
return {
|
||||
@ -138,7 +143,12 @@ const InternalList = (props) => {
|
||||
)}
|
||||
>
|
||||
<div className="nb-list-container">
|
||||
<FormLayout layout={layout}>
|
||||
<FormLayout
|
||||
layout={layout}
|
||||
labelAlign={labelAlign}
|
||||
labelWidth={layout === 'horizontal' ? labelWidth : null}
|
||||
labelWrap={labelWrap}
|
||||
>
|
||||
<AntdList
|
||||
{...props}
|
||||
pagination={!meta || !field.value?.length ? false : paginationProps}
|
||||
|
@ -21,12 +21,12 @@ describe('Pagination', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div>
|
||||
<ul
|
||||
class="ant-pagination css-dev-only-do-not-override-wwtqkl"
|
||||
class="ant-pagination css-dev-only-do-not-override-11aiz3o"
|
||||
>
|
||||
<li
|
||||
aria-disabled="true"
|
||||
@ -131,7 +131,7 @@ describe('Pagination', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
/>
|
||||
</div>
|
||||
|
@ -86,7 +86,8 @@ const InternalRemoteSelect = withDynamicSchemaProps(
|
||||
|
||||
const operator = useMemo(() => {
|
||||
if (targetField?.interface) {
|
||||
return getInterface(targetField.interface)?.filterable?.operators[0].value || '$includes';
|
||||
const initialOperator = getInterface(targetField.interface)?.filterable?.operators[0].value || '$includes';
|
||||
return initialOperator !== '$eq' ? initialOperator : '$includes';
|
||||
}
|
||||
return '$includes';
|
||||
}, [targetField]);
|
||||
|
@ -105,7 +105,7 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
|
||||
return css`
|
||||
.nb-action-link {
|
||||
margin: -${token.paddingContentVerticalLG}px -${token.marginSM}px;
|
||||
padding: ${token.paddingContentVerticalLG}px ${token.margin}px;
|
||||
padding: ${token.paddingContentVerticalLG}px ${token.paddingSM + 4}px;
|
||||
}
|
||||
`;
|
||||
}, [token.paddingContentVerticalLG, token.marginSM]);
|
||||
@ -196,6 +196,9 @@ const useTableColumns = (props: { showDel?: any; isSubTable?: boolean }, paginat
|
||||
deleteCount: 1,
|
||||
});
|
||||
field.value.splice(fieldIndex, 1);
|
||||
setTimeout(() => {
|
||||
field.value[field.value.length] = null;
|
||||
});
|
||||
return field.onInput(field.value);
|
||||
});
|
||||
}}
|
||||
@ -232,8 +235,8 @@ const SortableRow = (props: {
|
||||
const { ref, inView } = useInView({
|
||||
threshold: 0,
|
||||
triggerOnce: true,
|
||||
initialInView: !!process.env.__E2E__ || isInSubTable || props.rowIndex < INITIAL_ROWS_NUMBER,
|
||||
skip: !!process.env.__E2E__ || isInSubTable || props.rowIndex < INITIAL_ROWS_NUMBER,
|
||||
initialInView: !!process.env.__E2E__ || isInSubTable || (props.rowIndex || 0) < INITIAL_ROWS_NUMBER,
|
||||
skip: !!process.env.__E2E__ || isInSubTable,
|
||||
});
|
||||
|
||||
const classObj = useMemo(() => {
|
||||
@ -617,6 +620,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
} = { ...others1, ...others2 } as any;
|
||||
const field = useArrayField(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();
|
||||
@ -889,7 +893,6 @@ export const Table: any = withDynamicSchemaProps(
|
||||
expandedRowKeys: expandedKeys,
|
||||
};
|
||||
}, [expandedKeys, onExpandValue]);
|
||||
|
||||
return (
|
||||
// If spinning is set to undefined, it will cause the subtable to always display loading, so we need to convert it here
|
||||
<Spin spinning={!!loading}>
|
||||
@ -910,6 +913,7 @@ export const Table: any = withDynamicSchemaProps(
|
||||
columns={columns}
|
||||
expandable={expandable}
|
||||
field={field}
|
||||
size={size}
|
||||
/>
|
||||
</Spin>
|
||||
);
|
||||
|
@ -303,6 +303,30 @@ export const TableBlockDesigner = () => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SchemaSettingsSelectItem
|
||||
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'],
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SchemaSettingsConnectDataBlocks type={FilterBlockType.TABLE} emptyDescription={t('No blocks to connect')} />
|
||||
{supportTemplate && <SchemaSettingsDivider />}
|
||||
{supportTemplate && (
|
||||
|
@ -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',
|
||||
|
@ -20,11 +20,11 @@ describe('UnixTimestamp', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
class="ant-picker css-dev-only-do-not-override-wwtqkl"
|
||||
class="ant-picker css-dev-only-do-not-override-11aiz3o"
|
||||
>
|
||||
<div
|
||||
class="ant-picker-input"
|
||||
@ -77,7 +77,7 @@ describe('UnixTimestamp', () => {
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="css-dev-only-do-not-override-wwtqkl ant-app"
|
||||
class="css-dev-only-do-not-override-11aiz3o ant-app"
|
||||
style="height: 100%;"
|
||||
>
|
||||
<div
|
||||
|
@ -446,14 +446,7 @@ export function Uploader({ rules, ...props }: UploadProps) {
|
||||
</Tooltip>
|
||||
</div>
|
||||
{selectable && QRCodeUploader && (
|
||||
<QRCodeUploader
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
// TODO
|
||||
console.log(value);
|
||||
// onChange(value);
|
||||
}}
|
||||
/>
|
||||
<QRCodeUploader disabled={disabled} multiple={multiple} value={value} onChange={onChange} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -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') {
|
||||
|
@ -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();
|
||||
}}
|
||||
/>
|
||||
|
@ -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<Field>();
|
||||
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();
|
||||
},
|
||||
};
|
||||
|
@ -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<string, any>;
|
||||
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 };
|
||||
}
|
||||
|
@ -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 (
|
||||
<SchemaSettingsModalItem
|
||||
title={t('Layout')}
|
||||
schema={
|
||||
{
|
||||
type: 'object',
|
||||
title: t('Set block layout'),
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
{ label: t('Vertical'), value: Layout.VERTICAL },
|
||||
{ label: t('Horizontal'), value: Layout.HORIZONTAL },
|
||||
],
|
||||
required: true,
|
||||
default: fieldSchema?.['x-component-props']?.layout || Layout.VERTICAL,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
},
|
||||
labelAlign: {
|
||||
title: t('Label align'),
|
||||
type: 'string',
|
||||
default: fieldSchema?.['x-component-props']?.['labelAlign'] || 'left',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
enum: [
|
||||
{ label: "{{t('Right')}}", value: 'right' },
|
||||
{ label: "{{t('Left')}}", value: 'left' },
|
||||
],
|
||||
'x-reactions': {
|
||||
dependencies: ['layout'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{ $deps[0]==="horizontal"}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labelWidth: {
|
||||
title: t('Label width'),
|
||||
type: 'string',
|
||||
default: fieldSchema?.['x-component-props']?.['labelWidth'] || 120,
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'InputNumber',
|
||||
'x-component-props': {
|
||||
addonAfter: 'px',
|
||||
},
|
||||
'x-validator': [
|
||||
{
|
||||
minimum: 50,
|
||||
},
|
||||
],
|
||||
'x-reactions': {
|
||||
dependencies: ['layout'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{ $deps[0]==="horizontal"}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
labelWrap: {
|
||||
type: 'string',
|
||||
title: t('When the Label exceeds the width'),
|
||||
enum: [
|
||||
{ label: t('Line break'), value: true },
|
||||
{ label: t('Ellipsis'), value: false },
|
||||
],
|
||||
default: fieldSchema?.['x-component-props']?.labelWrap !== false,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Radio.Group',
|
||||
'x-reactions': {
|
||||
dependencies: ['layout'],
|
||||
fulfill: {
|
||||
state: {
|
||||
visible: '{{ $deps[0]==="horizontal"}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ISchema
|
||||
}
|
||||
onSubmit={({ layout, labelAlign, labelWidth, labelWrap }) => {
|
||||
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();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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';
|
||||
|
@ -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)}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-nocobase-app",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"main": "src/index.js",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
|
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@nocobase/data-source-manager",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@nocobase/cache": "1.4.0-alpha.2",
|
||||
"@nocobase/database": "1.4.0-alpha.2",
|
||||
"@nocobase/resourcer": "1.4.0-alpha.2",
|
||||
"@nocobase/utils": "1.4.0-alpha.2",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
},
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ 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;
|
||||
|
||||
@ -79,6 +80,7 @@ export abstract class DataSource extends EventEmitter {
|
||||
for (const [fn, options] of middlewares) {
|
||||
this.resourceManager.use(fn, options);
|
||||
}
|
||||
|
||||
this['_used'] = true;
|
||||
}
|
||||
|
||||
@ -91,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);
|
||||
};
|
||||
}
|
||||
|
||||
@ -117,15 +121,16 @@ export abstract class DataSource extends EventEmitter {
|
||||
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) {
|
||||
@ -133,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();
|
||||
}
|
||||
|
||||
@ -141,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,
|
||||
});
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/database",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@nocobase/utils": "1.4.0-alpha.2",
|
||||
"async-mutex": "^0.3.2",
|
||||
"chalk": "^4.1.1",
|
||||
"cron-parser": "4.4.0",
|
||||
|
@ -189,10 +189,6 @@ export class Collection<
|
||||
return this.model.primaryKeyAttribute;
|
||||
}
|
||||
|
||||
isMultiFilterTargetKey() {
|
||||
return Array.isArray(this.filterTargetKey) && this.filterTargetKey.length > 1;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.options.name;
|
||||
}
|
||||
@ -225,6 +221,10 @@ export class Collection<
|
||||
}
|
||||
}
|
||||
|
||||
isMultiFilterTargetKey() {
|
||||
return Array.isArray(this.filterTargetKey) && this.filterTargetKey.length > 1;
|
||||
}
|
||||
|
||||
tableName() {
|
||||
const { name, tableName } = this.options;
|
||||
const tName = tableName || name;
|
||||
@ -313,6 +313,20 @@ export class Collection<
|
||||
},
|
||||
});
|
||||
|
||||
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;
|
||||
@ -639,9 +653,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);
|
||||
@ -909,10 +928,6 @@ export class Collection<
|
||||
}
|
||||
|
||||
unavailableActions() {
|
||||
if (this.options.template === 'file') {
|
||||
return ['create', 'update', 'destroy'];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -234,7 +234,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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(',');
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/devtools",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"@nocobase/client": "1.4.0-alpha.2",
|
||||
"@nocobase/test": "1.4.0-alpha.2",
|
||||
"@types/koa": "^2.13.4",
|
||||
"@types/koa-bodyparser": "^4.3.4",
|
||||
"@types/lodash": "^4.14.177",
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/evaluators",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"mathjs": "^10.6.0"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/logger",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"description": "nocobase logging library",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
|
@ -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 });
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@nocobase/resourcer",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"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.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"koa-compose": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -306,7 +306,9 @@ export class ResourceManager {
|
||||
}
|
||||
|
||||
middleware({ prefix, accessors, skipIfDataSourceExists = false }: KoaMiddlewareOptions = {}) {
|
||||
return async (ctx: ResourcerContext, next: () => Promise<any>) => {
|
||||
const self = this;
|
||||
|
||||
return async function resourcerMiddleware(ctx: ResourcerContext, next: () => Promise<any>) {
|
||||
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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/sdk",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/server",
|
||||
"version": "1.4.0-alpha",
|
||||
"version": "1.4.0-alpha.2",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"license": "AGPL-3.0",
|
||||
@ -10,18 +10,18 @@
|
||||
"@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/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",
|
||||
"@nocobase/acl": "1.4.0-alpha.2",
|
||||
"@nocobase/actions": "1.4.0-alpha.2",
|
||||
"@nocobase/auth": "1.4.0-alpha.2",
|
||||
"@nocobase/cache": "1.4.0-alpha.2",
|
||||
"@nocobase/data-source-manager": "1.4.0-alpha.2",
|
||||
"@nocobase/database": "1.4.0-alpha.2",
|
||||
"@nocobase/evaluators": "1.4.0-alpha.2",
|
||||
"@nocobase/logger": "1.4.0-alpha.2",
|
||||
"@nocobase/resourcer": "1.4.0-alpha.2",
|
||||
"@nocobase/sdk": "1.4.0-alpha.2",
|
||||
"@nocobase/telemetry": "1.4.0-alpha.2",
|
||||
"@nocobase/utils": "1.4.0-alpha.2",
|
||||
"@types/decompress": "4.2.7",
|
||||
"@types/ini": "^1.3.31",
|
||||
"@types/koa-send": "^4.1.3",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user