mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-01 18:52:20 +08:00
Merge branch 'main' into develop
This commit is contained in:
commit
b9d5ad5645
22
.github/workflows/auto-merge.yml
vendored
22
.github/workflows/auto-merge.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: auto-merge
|
||||
name: Auto merge main -> next
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@ -13,17 +13,29 @@ jobs:
|
||||
push-commit:
|
||||
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.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
|
||||
ssh-key: ${{ secrets.NOCOBASE_DEPLOY_KEY }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
fetch-depth: 0
|
||||
- name: main -> next(nocobase)
|
||||
run: |
|
||||
git config --global user.email "actions@github.com"
|
||||
git config --global user.name "GitHub Actions Bot"
|
||||
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 checkout main
|
||||
git pull origin main
|
||||
git checkout next
|
||||
@ -33,7 +45,7 @@ jobs:
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
branch: next
|
||||
ssh: true
|
||||
github_token: ${{ steps.app-token.outputs.token }}
|
||||
repository: nocobase/nocobase
|
||||
tags: true
|
||||
atomic: true
|
||||
|
2
.github/workflows/build-docker-image.yml
vendored
2
.github/workflows/build-docker-image.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Build Docker Image
|
||||
name: Build docker image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
2
.github/workflows/build-pro-image.yml
vendored
2
.github/workflows/build-pro-image.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Build Pro Image
|
||||
name: Build pro image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
2
.github/workflows/changelog-and-release.yml
vendored
2
.github/workflows/changelog-and-release.yml
vendored
@ -70,7 +70,7 @@ jobs:
|
||||
- name: Run script
|
||||
shell: bash
|
||||
run: |
|
||||
node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }}
|
||||
node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }} --cmsURL ${{ secrets.CMS_URL }} --cmsToken ${{ secrets.CMS_TOKEN }}
|
||||
env:
|
||||
PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }}
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
56
.github/workflows/deploy-client-docs.yml
vendored
56
.github/workflows/deploy-client-docs.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: deploy client docs
|
||||
name: Deploy client docs
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@ -25,30 +25,30 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- name: Build zh-CN
|
||||
run: yarn doc build core/client --lang=zh-CN
|
||||
- name: Build en-US
|
||||
run: yarn doc build core/client --lang=en-US
|
||||
- name: Set tags
|
||||
id: set-tags
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "::set-output name=tags::${{ github.ref_name }}"
|
||||
else
|
||||
echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}"
|
||||
fi
|
||||
- name: copy files via ssh - ${{ steps.set-tags.outputs.tags }}
|
||||
uses: appleboy/scp-action@v0.1.4
|
||||
with:
|
||||
host: ${{ secrets.CN_CLIENT_HOST }}
|
||||
username: ${{ secrets.CN_CLIENT_USERNAME }}
|
||||
key: ${{ secrets.CN_CLIENT_KEY }}
|
||||
port: ${{ secrets.CN_CLIENT_PORT }}
|
||||
source: "packages/core/client/dist/*"
|
||||
target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- name: Build zh-CN
|
||||
run: yarn doc build core/client --lang=zh-CN
|
||||
- name: Build en-US
|
||||
run: yarn doc build core/client --lang=en-US
|
||||
- name: Set tags
|
||||
id: set-tags
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "::set-output name=tags::${{ github.ref_name }}"
|
||||
else
|
||||
echo "::set-output name=tags::pr-${{ github.event.pull_request.number }}"
|
||||
fi
|
||||
- name: copy files via ssh - ${{ steps.set-tags.outputs.tags }}
|
||||
uses: appleboy/scp-action@v0.1.4
|
||||
with:
|
||||
host: ${{ secrets.CN_CLIENT_HOST }}
|
||||
username: ${{ secrets.CN_CLIENT_USERNAME }}
|
||||
key: ${{ secrets.CN_CLIENT_KEY }}
|
||||
port: ${{ secrets.CN_CLIENT_PORT }}
|
||||
source: 'packages/core/client/dist/*'
|
||||
target: ${{ secrets.CN_CLIENT_TARGET }}/${{ steps.set-tags.outputs.tags }}
|
||||
|
39
.github/workflows/get-nocobase-app-token.yml
vendored
39
.github/workflows/get-nocobase-app-token.yml
vendored
@ -1,39 +0,0 @@
|
||||
name: Get nocobase app github token
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
token:
|
||||
value: ${{ jobs.get-app-token.outputs.token }}
|
||||
user-id:
|
||||
value: ${{ jobs.get-app-token.outputs.user-id }}
|
||||
app-slug:
|
||||
value: ${{ jobs.get-app-token.outputs.app-slug }}
|
||||
|
||||
jobs:
|
||||
get-app-token:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
token: ${{ steps.encrypt-token.outputs.token }}
|
||||
app-slug: ${{ steps.app-token.outputs.app-slug }}
|
||||
user-id: ${{ steps.get-user-id.outputs.user-id }}
|
||||
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.PRO_PLUGIN_REPOS), ',') }}
|
||||
skip-token-revoke: true
|
||||
- name: Encrypt token
|
||||
id: encrypt-token
|
||||
shell: bash
|
||||
run: |
|
||||
APP_TOKEN=${{ steps.app-token.outputs.token }};
|
||||
ENCRYPTED_SECRET=$(echo -n "$APP_TOKEN" | openssl enc -aes-256-cbc -pbkdf2 -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}" | base64 -w 0);
|
||||
echo "token=$ENCRYPTED_SECRET" >> $GITHUB_OUTPUT
|
||||
- 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 }}
|
@ -1,4 +1,4 @@
|
||||
name: manual-build-pr-docker-image
|
||||
name: Manual build pr docker image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
2
.github/workflows/manual-build-pro-image.yml
vendored
2
.github/workflows/manual-build-pro-image.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: manual-build-pro-image
|
||||
name: Manual build pro image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
name: Build Pro Plugin Docker Image
|
||||
name: Build pro plugin docker image
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
2
.github/workflows/manual-e2e.yml
vendored
2
.github/workflows/manual-e2e.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: manual-e2e
|
||||
name: Manual e2e
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
2
.github/workflows/manual-release.yml
vendored
2
.github/workflows/manual-release.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: manual-release
|
||||
name: Manual release
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
2
.github/workflows/nocobase-test-backend.yml
vendored
2
.github/workflows/nocobase-test-backend.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: NocoBase Backend Test
|
||||
name: NocoBase backend test
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
4
.github/workflows/nocobase-test-frontend.yml
vendored
4
.github/workflows/nocobase-test-frontend.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: NocoBase FrontEnd Test
|
||||
name: NocoBase frontEnd test
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@ -33,7 +33,7 @@ jobs:
|
||||
frontend-test:
|
||||
strategy:
|
||||
matrix:
|
||||
node_version: [ '18' ]
|
||||
node_version: ['18']
|
||||
runs-on: ubuntu-latest
|
||||
container: node:${{ matrix.node_version }}
|
||||
steps:
|
||||
|
4
.github/workflows/release-next.yml
vendored
4
.github/workflows/release-next.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Release Next
|
||||
name: Release next
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@ -148,7 +148,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch
|
||||
node scripts/release/changelogAndRelease.js --ver alpha
|
||||
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 }}
|
||||
|
@ -5,6 +5,12 @@ 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.32-beta](https://github.com/nocobase/nocobase/compare/v1.3.31-beta...v1.3.32-beta) - 2024-10-13
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]** required relational field still triggers validation error after selecting a value with a variable in data scope ([#5399](https://github.com/nocobase/nocobase/pull/5399)) by @katherinehhh
|
||||
|
||||
## [v1.3.31-beta](https://github.com/nocobase/nocobase/compare/v1.3.30-beta...v1.3.31-beta) - 2024-10-11
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
@ -5,6 +5,12 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||
|
||||
## [v1.3.32-beta](https://github.com/nocobase/nocobase/compare/v1.3.31-beta...v1.3.32-beta) - 2024-10-13
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]** 关系字段设置必填,数据范围中设置变量后,选中值却报字段必填不通过 ([#5399](https://github.com/nocobase/nocobase/pull/5399)) by @katherinehhh
|
||||
|
||||
## [v1.3.31-beta](https://github.com/nocobase/nocobase/compare/v1.3.30-beta...v1.3.31-beta) - 2024-10-11
|
||||
|
||||
### 🐛 修复
|
||||
|
2
packages/core/cache/src/cache-manager.ts
vendored
2
packages/core/cache/src/cache-manager.ts
vendored
@ -100,7 +100,7 @@ export class CacheManager {
|
||||
|
||||
async createCache(options: { name: string; prefix?: string; store?: string; [key: string]: any }) {
|
||||
const { name, prefix, store = this.defaultStore, ...config } = options;
|
||||
if (!lodash.isEmpty(config)) {
|
||||
if (!lodash.isEmpty(config) || store === 'memory') {
|
||||
const newStore = await this.createStore({ name, storeType: store, ...config });
|
||||
return this.newCache({ name, prefix, store: newStore });
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ describe('remotePlugins', () => {
|
||||
expect(requirejs.requirejs.config).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('If there is devDynamicImport and devDynamicImport returns partial, remote API will be requested', async () => {
|
||||
it.skip('If there is devDynamicImport and devDynamicImport returns partial, remote API will be requested', async () => {
|
||||
const remoteFn = vi.fn();
|
||||
const mockPluginsModules = (pluginData, resolve) => {
|
||||
remoteFn();
|
||||
|
@ -7,10 +7,10 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import type { DevDynamicImport } from '../Application';
|
||||
import type { Plugin } from '../Plugin';
|
||||
import type { PluginData } from '../PluginManager';
|
||||
import type { RequireJS } from './requirejs';
|
||||
import type { DevDynamicImport } from '../Application';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -131,7 +131,10 @@ export async function getPlugins(options: GetPluginsOption): Promise<Array<[stri
|
||||
return res;
|
||||
}
|
||||
|
||||
const remotePluginList = await getRemotePlugins(requirejs, remotePlugins);
|
||||
res.push(...remotePluginList);
|
||||
if (res.length === 0) {
|
||||
const remotePluginList = await getRemotePlugins(requirejs, remotePlugins);
|
||||
res.push(...remotePluginList);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -1509,7 +1509,8 @@ export const useAssociationNames = (dataSource?: string) => {
|
||||
['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(collectionField.type);
|
||||
|
||||
// 根据联动规则中条件的字段获取一些 appends
|
||||
if (s['x-linkage-rules']) {
|
||||
// 需要排除掉子表格和子表单中的联动规则
|
||||
if (s['x-linkage-rules'] && !isSubMode(s)) {
|
||||
const collectAppends = (obj) => {
|
||||
const type = Object.keys(obj)[0] || '$and';
|
||||
const list = obj[type];
|
||||
|
@ -8,7 +8,11 @@
|
||||
*/
|
||||
|
||||
import { expect, test } from '@nocobase/test/e2e';
|
||||
import { shouldRefreshDataWhenSubpageIsClosedByPageMenu, submitInReferenceTemplateBlock } from './templates';
|
||||
import {
|
||||
shouldRefreshDataWhenSubpageIsClosedByPageMenu,
|
||||
submitInReferenceTemplateBlock,
|
||||
createFormSubmit,
|
||||
} from './templates';
|
||||
|
||||
test.describe('Submit: should refresh data after submit', () => {
|
||||
test('submit in reference template block', async ({ page, mockPage, clearBlockTemplates, mockRecord }) => {
|
||||
@ -108,3 +112,34 @@ test.describe('Submit: should refresh data after submit', () => {
|
||||
await page.getByRole('button', { name: '1234567890', exact: true }).click();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Submit: After successful submission', () => {
|
||||
test('return to the previous popup or page as default value', async ({ page, mockPage, mockRecord }) => {
|
||||
const nocoPage = await mockPage(createFormSubmit).waitForInit();
|
||||
await nocoPage.goto();
|
||||
|
||||
await page.getByLabel('action-Action-Add new-create-').click();
|
||||
await page.getByLabel('action-Action-Submit-submit-').click();
|
||||
await expect(page.getByTestId('drawer-Action.Container-users-Add record')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('return to the previous popup or page change to stay on the current popup or page', async ({
|
||||
page,
|
||||
mockPage,
|
||||
mockRecord,
|
||||
}) => {
|
||||
const nocoPage = await mockPage(createFormSubmit).waitForInit();
|
||||
await nocoPage.goto();
|
||||
|
||||
await page.getByLabel('action-Action-Add new-create-').click();
|
||||
|
||||
await page.getByLabel('action-Action-Submit-submit-').hover();
|
||||
await page.getByLabel('designer-schema-settings-Action-actionSettings:createSubmit-users').hover();
|
||||
|
||||
await page.getByText('After successful submission').click();
|
||||
await page.getByLabel('Stay on the current popup or').check();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
await page.getByLabel('action-Action-Submit-submit-').click();
|
||||
await expect(page.getByTestId('drawer-Action.Container-users-Add record')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
@ -1602,3 +1602,360 @@ export const shouldRefreshDataWhenSubpageIsClosedByPageMenu = {
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const createFormSubmit = {
|
||||
pageSchema: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Page',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
c9ar217262w: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'page:addBlock',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
'943651fwz8t': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
g9fq98c2s4n: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
'4d3afoagc5g': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-decorator': 'TableBlockProvider',
|
||||
'x-acl-action': 'users:list',
|
||||
'x-use-decorator-props': 'useTableBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
collection: 'users',
|
||||
dataSource: 'main',
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 20,
|
||||
},
|
||||
rowKey: 'id',
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:table',
|
||||
'x-component': 'CardItem',
|
||||
'x-filter-targets': [],
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
actions: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-initializer': 'table:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 'var(--nb-spacing)',
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
afp94e3l423: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-action': 'create',
|
||||
'x-acl-action': 'create',
|
||||
title: "{{t('Add new')}}",
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:addNew',
|
||||
'x-component': 'Action',
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
type: 'primary',
|
||||
component: 'CreateRecordAction',
|
||||
icon: 'PlusOutlined',
|
||||
},
|
||||
'x-action-context': {
|
||||
dataSource: 'main',
|
||||
collection: 'users',
|
||||
},
|
||||
'x-align': 'right',
|
||||
'x-acl-action-props': {
|
||||
skipScopeCheck: true,
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
drawer: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{ t("Add record") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
tabs: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'popup:addTab',
|
||||
'x-initializer-props': {
|
||||
gridInitializer: 'popup:addNew:addBlock',
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
tab1: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{t("Add new")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'popup:addNew:addBlock',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
umjudxvb3ri: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
to6yrk48a1e: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
zfuiyepfpo8: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-acl-action-props': {
|
||||
skipScopeCheck: true,
|
||||
},
|
||||
'x-acl-action': 'users:create',
|
||||
'x-decorator': 'FormBlockProvider',
|
||||
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
dataSource: 'main',
|
||||
collection: 'users',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:createForm',
|
||||
'x-component': 'CardItem',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
'67zq3yb6rg0': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'FormV2',
|
||||
'x-use-component-props': 'useCreateFormBlockProps',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'form:configureFields',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
'x-uid': 'tyztr48d01m',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
'1cslc0bzwan': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-initializer': 'createForm:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
layout: 'one-column',
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
rjfxzva092d: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
title: '{{ t("Submit") }}',
|
||||
'x-action': 'submit',
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useCreateActionProps',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:createSubmit',
|
||||
'x-component-props': {
|
||||
type: 'primary',
|
||||
htmlType: 'submit',
|
||||
},
|
||||
'x-action-settings': {
|
||||
triggerWorkflows: [],
|
||||
},
|
||||
type: 'void',
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
'x-uid': 'jpbq9zyexb9',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'xsv37mjii6o',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': '2j67lyyth1f',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'zcli14m52d6',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '63hxohd7td3',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'm6uu6mtlopn',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'x5nqkk6whbz',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'kaxuorp0kwz',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'qka0657zo5s',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '857bxboz38t',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'qxh2sf5itxw',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '3nsvi44j20t',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
bk1pbg52rcs: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'array',
|
||||
'x-initializer': 'table:configureColumns',
|
||||
'x-component': 'TableV2',
|
||||
'x-use-component-props': 'useTableBlockProps',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
actions: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{ t("Actions") }}',
|
||||
'x-action-column': 'actions',
|
||||
'x-decorator': 'TableV2.Column.ActionBar',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||
'x-initializer': 'table:configureItemActions',
|
||||
'x-settings': 'fieldSettings:TableColumn',
|
||||
'x-toolbar-props': {
|
||||
initializer: 'table:configureItemActions',
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
properties: {
|
||||
'9tql6edotky': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-decorator': 'DndContext',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
split: '|',
|
||||
},
|
||||
'x-app-version': '1.4.0-alpha',
|
||||
'x-uid': 'zrlg6n04kd0',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'uv7q2tnh9mc',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'g76idw88ik8',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': 'h0mnhu6ihsd',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '3xdo6vt2c07',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'et8gznfo7r7',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '18w03j8qcv6',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'qvl1ar53zpz',
|
||||
'x-async': true,
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
|
@ -25,7 +25,7 @@ export const UpdateRecordActionInitializer = (props) => {
|
||||
'x-action-settings': {
|
||||
assignedValues: {},
|
||||
onSuccess: {
|
||||
manualClose: true,
|
||||
manualClose: false,
|
||||
redirecting: false,
|
||||
successMessage: '{{t("Updated successfully")}}',
|
||||
},
|
||||
|
@ -76,6 +76,10 @@ test.describe('set default value', () => {
|
||||
await expect(
|
||||
page.getByLabel('block-item-CollectionField-general-form-general.longText-longText').getByRole('textbox'),
|
||||
).toHaveValue('new value');
|
||||
|
||||
// 再次打开设置默认值的弹窗,变量输入框应该显示设置的变量值
|
||||
await openDialog('longText');
|
||||
await expect(page.getByLabel('block-item-VariableInput-')).toHaveText(/Current form \/ singleLineText/);
|
||||
});
|
||||
|
||||
test('subform: basic fields', async ({ page, mockPage }) => {
|
||||
|
@ -843,7 +843,6 @@ test.describe('actions schema settings', () => {
|
||||
|
||||
const expectNewValue = async (value: string) => {
|
||||
await page.getByLabel('action-Action.Link-Update record-customize:update-users2-table-0').click();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await page.getByLabel('action-Action-Refresh-refresh').click();
|
||||
await expect(page.getByLabel('block-item-CardItem-users2-').getByText(value)).toBeVisible();
|
||||
};
|
||||
|
@ -5124,3 +5124,236 @@ export const zIndexOfSubpage = {
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
export const zIndexEditProfile = {
|
||||
pageSchema: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Page',
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
'17v3l3wra1q': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'page:addBlock',
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
tpentznu41j: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
'16wj734kw8c': {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
gh2evps4nft: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-decorator': 'TableBlockProvider',
|
||||
'x-acl-action': 'users:list',
|
||||
'x-use-decorator-props': 'useTableBlockDecoratorProps',
|
||||
'x-decorator-props': {
|
||||
collection: 'users',
|
||||
dataSource: 'main',
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 20,
|
||||
},
|
||||
rowKey: 'id',
|
||||
showIndex: true,
|
||||
dragSort: false,
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': 'blockSettings:table',
|
||||
'x-component': 'CardItem',
|
||||
'x-filter-targets': [],
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
actions: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-initializer': 'table:configureActions',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 'var(--nb-spacing)',
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.3.32-beta',
|
||||
'x-uid': 'jlclfunyxlo',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
juqs9wupfcc: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'array',
|
||||
'x-initializer': 'table:configureColumns',
|
||||
'x-component': 'TableV2',
|
||||
'x-use-component-props': 'useTableBlockProps',
|
||||
'x-component-props': {
|
||||
rowKey: 'id',
|
||||
rowSelection: {
|
||||
type: 'checkbox',
|
||||
},
|
||||
},
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
actions: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{ t("Actions") }}',
|
||||
'x-action-column': 'actions',
|
||||
'x-decorator': 'TableV2.Column.ActionBar',
|
||||
'x-component': 'TableV2.Column',
|
||||
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||
'x-initializer': 'table:configureItemActions',
|
||||
'x-settings': 'fieldSettings:TableColumn',
|
||||
'x-toolbar-props': {
|
||||
initializer: 'table:configureItemActions',
|
||||
},
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
onge8etkwkq: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-decorator': 'DndContext',
|
||||
'x-component': 'Space',
|
||||
'x-component-props': {
|
||||
split: '|',
|
||||
},
|
||||
'x-app-version': '1.3.32-beta',
|
||||
properties: {
|
||||
'44abnpgy9hl': {
|
||||
'x-uid': '2m4j1wrmt2k',
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: 'open subpage',
|
||||
'x-action': 'view',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'actionSettings:view',
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'page',
|
||||
iconColor: '#1677FF',
|
||||
danger: false,
|
||||
},
|
||||
'x-action-context': {
|
||||
dataSource: 'main',
|
||||
collection: 'users',
|
||||
},
|
||||
'x-decorator': 'ACLActionProvider',
|
||||
'x-designer-props': {
|
||||
linkageAction: true,
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{ t("View record") }}',
|
||||
'x-component': 'Action.Container',
|
||||
'x-component-props': {
|
||||
className: 'nb-action-popup',
|
||||
},
|
||||
properties: {
|
||||
tabs: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Tabs',
|
||||
'x-component-props': {},
|
||||
'x-initializer': 'popup:addTab',
|
||||
properties: {
|
||||
tab1: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '{{t("Details")}}',
|
||||
'x-component': 'Tabs.TabPane',
|
||||
'x-designer': 'Tabs.Designer',
|
||||
'x-component-props': {},
|
||||
properties: {
|
||||
grid: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-initializer': 'popup:common:addBlock',
|
||||
'x-uid': 'pncs8uoz02h',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'i1n4wf3wqt8',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '537y7twqq1x',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'j652bs2ocx7',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'lmmfco4ti1k',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'qxsdgdxd7zd',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'rxecd82tr2t',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': '8mdf3toqg65',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'x2e6vc3l16a',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'eqlzylp6c53',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': '2e8trd4olie',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'ay7xv8zc868',
|
||||
'x-async': true,
|
||||
'x-index': 1,
|
||||
},
|
||||
};
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import { expect, test } from '@nocobase/test/e2e';
|
||||
import { T2797, T2838, zIndexOfSubpage } from './templatesOfBug';
|
||||
import { T2797, T2838, zIndexEditProfile, zIndexOfSubpage } from './templatesOfBug';
|
||||
|
||||
test.describe('z-index of dialog', () => {
|
||||
test('edit block title', async ({ page, mockPage }) => {
|
||||
@ -73,4 +73,32 @@ test.describe('z-index of dialog', () => {
|
||||
.click();
|
||||
await expect(page.getByText('Add condition', { exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('edit profile', async ({ page, mockPage }) => {
|
||||
await mockPage(zIndexEditProfile).goto();
|
||||
|
||||
// open subpage, and then open the Edit Profile drawer
|
||||
await page.getByLabel('action-Action.Link-open').click();
|
||||
await page.getByTestId('user-center-button').hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit profile' }).click();
|
||||
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).toBeVisible();
|
||||
|
||||
// click the Cancel button to close the drawer
|
||||
await page.getByLabel('action-Action-Cancel').click();
|
||||
await expect(page.getByTestId('drawer-Action.Drawer-Edit profile')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('change password', async ({ page, mockPage }) => {
|
||||
await mockPage(zIndexEditProfile).goto();
|
||||
|
||||
// open subpage, and then open the Change password drawer
|
||||
await page.getByLabel('action-Action.Link-open').click();
|
||||
await page.getByTestId('user-center-button').hover();
|
||||
await page.getByRole('menuitem', { name: 'Change password' }).click();
|
||||
await expect(page.getByTestId('drawer-Action.Drawer-Change password')).toBeVisible();
|
||||
|
||||
// click the Cancel button to close the drawer
|
||||
await page.getByLabel('action-Action-Cancel').click();
|
||||
await expect(page.getByTestId('drawer-Action.Drawer-Change password')).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ const openSizeWidthMap = new Map<OpenSize, string>([
|
||||
]);
|
||||
export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
|
||||
(props) => {
|
||||
const { footerNodeName = 'Action.Drawer.Footer', ...others } = props;
|
||||
const { footerNodeName = 'Action.Drawer.Footer', zIndex: _zIndex, ...others } = props;
|
||||
const { visible, setVisible, openSize = 'middle', drawerProps, modalProps } = useActionContext();
|
||||
const schema = useFieldSchema();
|
||||
const field = useField();
|
||||
@ -63,7 +63,7 @@ export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
|
||||
useSetAriaLabelForDrawer(visible);
|
||||
}
|
||||
|
||||
const zIndex = parentZIndex + (props.level || 0);
|
||||
const zIndex = _zIndex || parentZIndex + (props.level || 0);
|
||||
|
||||
return (
|
||||
<zIndexContext.Provider value={zIndex}>
|
||||
|
@ -39,7 +39,7 @@ const openSizeWidthMap = new Map<OpenSize, string>([
|
||||
|
||||
export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = observer(
|
||||
(props) => {
|
||||
const { footerNodeName = 'Action.Modal.Footer', width, ...others } = props;
|
||||
const { footerNodeName = 'Action.Modal.Footer', width, zIndex: _zIndex, ...others } = props;
|
||||
const { visible, setVisible, openSize = 'middle', modalProps } = useActionContext();
|
||||
const actualWidth = width ?? openSizeWidthMap.get(openSize);
|
||||
const schema = useFieldSchema();
|
||||
@ -71,7 +71,7 @@ export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = obse
|
||||
useSetAriaLabelForModal(visible);
|
||||
}
|
||||
|
||||
const zIndex = parentZIndex + (props.level || 0);
|
||||
const zIndex = _zIndex || parentZIndex + (props.level || 0);
|
||||
|
||||
return (
|
||||
<zIndexContext.Provider value={zIndex}>
|
||||
|
@ -47,6 +47,13 @@ const useStyles = genStyleHook('nb-action', (token) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'.ant-btn-icon': {
|
||||
marginInlineEnd: '0px !important',
|
||||
},
|
||||
'.nb-action-title': {
|
||||
marginInlineStart: '8px',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -377,7 +377,6 @@ function RenderButton({
|
||||
if (!designable && (field?.data?.hidden || !aclCtx)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem
|
||||
role="button"
|
||||
@ -393,7 +392,7 @@ function RenderButton({
|
||||
className={classnames(componentCls, hashId, className, 'nb-action')}
|
||||
type={type === 'danger' ? undefined : type}
|
||||
>
|
||||
{actionTitle}
|
||||
{actionTitle && <span className={icon ? 'nb-action-title' : null}>{actionTitle}</span>}
|
||||
<Designer {...designerProps} />
|
||||
</SortableItem>
|
||||
);
|
||||
|
@ -82,7 +82,8 @@ const InternalAssociationSelect = observer(
|
||||
if (
|
||||
linkageFields.includes(fieldPath?.props?.name) &&
|
||||
field.value &&
|
||||
fieldPath?.indexes?.[0] === field?.indexes?.[0]
|
||||
fieldPath?.indexes?.[0] === field?.indexes?.[0] &&
|
||||
fieldPath?.props?.name !== field.props.name
|
||||
) {
|
||||
field.setValue(undefined);
|
||||
setInnerValue(undefined);
|
||||
|
@ -255,11 +255,11 @@ export const getDateRanges = (props?: {
|
||||
};
|
||||
|
||||
function withParams(value: any[], params: { fieldOperator?: string; isParsingVariable?: boolean }) {
|
||||
if (params?.fieldOperator === '$dateBetween' || !params?.isParsingVariable) {
|
||||
return value;
|
||||
if (params?.isParsingVariable && params?.fieldOperator && params.fieldOperator !== '$dateBetween') {
|
||||
return value[0];
|
||||
}
|
||||
|
||||
return value[0];
|
||||
return value;
|
||||
}
|
||||
|
||||
export function inferPickerType(dateString: string): 'year' | 'month' | 'quarter' | 'date' {
|
||||
|
@ -325,7 +325,7 @@ const usePaginationProps = (pagination1, pagination2) => {
|
||||
},
|
||||
};
|
||||
}
|
||||
}, [pagination, t, showTotal]);
|
||||
}, [pagination, t, showTotal, field.value?.length]);
|
||||
|
||||
if (pagination2 === false) {
|
||||
return false;
|
||||
@ -333,7 +333,6 @@ const usePaginationProps = (pagination1, pagination2) => {
|
||||
if (!pagination2 && pagination1 === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return result.total <= result.pageSize ? false : result;
|
||||
};
|
||||
|
||||
|
@ -93,11 +93,7 @@ const testOpts = (ext: RegExp, options: { exclude?: string[]; include?: string[]
|
||||
};
|
||||
|
||||
export function getThumbnailPlaceholderURL(file, options: any = {}) {
|
||||
if (file.url) {
|
||||
return file.url;
|
||||
}
|
||||
for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i++) {
|
||||
// console.log(UPLOAD_PLACEHOLDER[i].ext, testOpts(UPLOAD_PLACEHOLDER[i].ext, options));
|
||||
if (UPLOAD_PLACEHOLDER[i].ext.test(file.extname || file.filename || file.url || file.name)) {
|
||||
if (testOpts(UPLOAD_PLACEHOLDER[i].ext, options)) {
|
||||
return UPLOAD_PLACEHOLDER[i].icon || UNKNOWN_FILE_ICON;
|
||||
|
@ -43,6 +43,7 @@ import React, {
|
||||
} from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { VariablesContext } from '../';
|
||||
import { APIClientProvider } from '../api-client/APIClientProvider';
|
||||
import { useAPIClient } from '../api-client/hooks/useAPIClient';
|
||||
import { ApplicationContext, LocationSearchContext, useApp, useLocationSearch } from '../application';
|
||||
@ -103,7 +104,6 @@ import { ChildDynamicComponent } from './EnableChildCollections/DynamicComponent
|
||||
import { FormLinkageRules } from './LinkageRules';
|
||||
import { useLinkageCollectionFieldOptions } from './LinkageRules/action-hooks';
|
||||
import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './LinkageRules/type';
|
||||
import { VariablesContext } from '../';
|
||||
export interface SchemaSettingsProps {
|
||||
title?: any;
|
||||
dn?: Designable;
|
||||
@ -747,6 +747,8 @@ export interface SchemaSettingsModalItemProps {
|
||||
hide?: boolean;
|
||||
/** 上下文中不需要当前记录 */
|
||||
noRecord?: boolean;
|
||||
/** 自定义 Modal 上下文 */
|
||||
ModalContextProvider?: React.FC;
|
||||
}
|
||||
export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props) => {
|
||||
const {
|
||||
@ -760,6 +762,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
||||
initialValues,
|
||||
width = 'fit-content',
|
||||
noRecord = false,
|
||||
ModalContextProvider = (props) => <>{props.children}</>,
|
||||
...others
|
||||
} = props;
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
@ -778,7 +781,7 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
||||
const blockOptions = useBlockContext();
|
||||
const { getOperators } = useOperators();
|
||||
const locationSearch = useLocationSearch();
|
||||
const variableOptions = useContext(VariablesContext);
|
||||
const variableOptions = useVariables();
|
||||
|
||||
// 解决变量`当前对象`值在弹窗中丢失的问题
|
||||
const { formValue: subFormValue, collection: subFormCollection } = useSubFormValue();
|
||||
@ -802,72 +805,74 @@ export const SchemaSettingsModalItem: FC<SchemaSettingsModalItemProps> = (props)
|
||||
{ title: schema.title || title, width },
|
||||
() => {
|
||||
return (
|
||||
<CollectOperators defaultOperators={getOperators()}>
|
||||
<VariablesContext.Provider value={variableOptions}>
|
||||
<BlockContext.Provider value={blockOptions}>
|
||||
<VariablePopupRecordProvider
|
||||
recordData={popupRecordVariable?.value}
|
||||
collection={popupRecordVariable?.collection}
|
||||
parent={{
|
||||
recordData: parentPopupRecordVariable?.value,
|
||||
collection: parentPopupRecordVariable?.collection,
|
||||
}}
|
||||
>
|
||||
<CollectionRecordProvider record={noRecord ? null : record}>
|
||||
<FormBlockContext.Provider value={formCtx}>
|
||||
<SubFormProvider value={{ value: subFormValue, collection: subFormCollection }}>
|
||||
<FormActiveFieldsProvider
|
||||
name="form"
|
||||
getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}
|
||||
>
|
||||
<LocationSearchContext.Provider value={locationSearch}>
|
||||
<BlockRequestContext_deprecated.Provider value={ctx}>
|
||||
<DataSourceApplicationProvider dataSourceManager={dm} dataSource={dataSourceKey}>
|
||||
<AssociationOrCollectionProvider
|
||||
allowNull
|
||||
collection={collection.name}
|
||||
association={association}
|
||||
>
|
||||
<SchemaComponentOptions scope={options.scope} components={options.components}>
|
||||
<FormLayout
|
||||
layout={'vertical'}
|
||||
className={css`
|
||||
// screen > 576px
|
||||
@media (min-width: 576px) {
|
||||
min-width: 520px;
|
||||
}
|
||||
<ModalContextProvider>
|
||||
<CollectOperators defaultOperators={getOperators()}>
|
||||
<VariablesContext.Provider value={variableOptions}>
|
||||
<BlockContext.Provider value={blockOptions}>
|
||||
<VariablePopupRecordProvider
|
||||
recordData={popupRecordVariable?.value}
|
||||
collection={popupRecordVariable?.collection}
|
||||
parent={{
|
||||
recordData: parentPopupRecordVariable?.value,
|
||||
collection: parentPopupRecordVariable?.collection,
|
||||
}}
|
||||
>
|
||||
<CollectionRecordProvider record={noRecord ? null : record}>
|
||||
<FormBlockContext.Provider value={formCtx}>
|
||||
<SubFormProvider value={{ value: subFormValue, collection: subFormCollection }}>
|
||||
<FormActiveFieldsProvider
|
||||
name="form"
|
||||
getActiveFieldsName={upLevelActiveFields?.getActiveFieldsName}
|
||||
>
|
||||
<LocationSearchContext.Provider value={locationSearch}>
|
||||
<BlockRequestContext_deprecated.Provider value={ctx}>
|
||||
<DataSourceApplicationProvider dataSourceManager={dm} dataSource={dataSourceKey}>
|
||||
<AssociationOrCollectionProvider
|
||||
allowNull
|
||||
collection={collection.name}
|
||||
association={association}
|
||||
>
|
||||
<SchemaComponentOptions scope={options.scope} components={options.components}>
|
||||
<FormLayout
|
||||
layout={'vertical'}
|
||||
className={css`
|
||||
// screen > 576px
|
||||
@media (min-width: 576px) {
|
||||
min-width: 520px;
|
||||
}
|
||||
|
||||
// screen <= 576px
|
||||
@media (max-width: 576px) {
|
||||
min-width: 320px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ApplicationContext.Provider value={app}>
|
||||
<APIClientProvider apiClient={apiClient}>
|
||||
<ConfigProvider locale={locale}>
|
||||
<SchemaComponent
|
||||
components={components}
|
||||
scope={scope}
|
||||
schema={schema}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</APIClientProvider>
|
||||
</ApplicationContext.Provider>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</AssociationOrCollectionProvider>
|
||||
</DataSourceApplicationProvider>
|
||||
</BlockRequestContext_deprecated.Provider>
|
||||
</LocationSearchContext.Provider>
|
||||
</FormActiveFieldsProvider>
|
||||
</SubFormProvider>
|
||||
</FormBlockContext.Provider>
|
||||
</CollectionRecordProvider>
|
||||
</VariablePopupRecordProvider>
|
||||
</BlockContext.Provider>
|
||||
</VariablesContext.Provider>
|
||||
</CollectOperators>
|
||||
// screen <= 576px
|
||||
@media (max-width: 576px) {
|
||||
min-width: 320px;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ApplicationContext.Provider value={app}>
|
||||
<APIClientProvider apiClient={apiClient}>
|
||||
<ConfigProvider locale={locale}>
|
||||
<SchemaComponent
|
||||
components={components}
|
||||
scope={scope}
|
||||
schema={schema}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
</APIClientProvider>
|
||||
</ApplicationContext.Provider>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
</AssociationOrCollectionProvider>
|
||||
</DataSourceApplicationProvider>
|
||||
</BlockRequestContext_deprecated.Provider>
|
||||
</LocationSearchContext.Provider>
|
||||
</FormActiveFieldsProvider>
|
||||
</SubFormProvider>
|
||||
</FormBlockContext.Provider>
|
||||
</CollectionRecordProvider>
|
||||
</VariablePopupRecordProvider>
|
||||
</BlockContext.Provider>
|
||||
</VariablesContext.Provider>
|
||||
</CollectOperators>
|
||||
</ModalContextProvider>
|
||||
);
|
||||
},
|
||||
theme,
|
||||
|
@ -109,17 +109,15 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
|
||||
FormLayout,
|
||||
VariableInput: (inputProps) => {
|
||||
return (
|
||||
<FlagProvider isInSubForm={isInSubForm} isInSubTable={isInSubTable} isInSetDefaultValueDialog>
|
||||
<VariableInput
|
||||
{...inputProps}
|
||||
value={inputProps.value || undefined}
|
||||
hideVariableButton={props?.hideVariableButton}
|
||||
/>
|
||||
</FlagProvider>
|
||||
<VariableInput
|
||||
{...inputProps}
|
||||
value={inputProps.value || undefined}
|
||||
hideVariableButton={props?.hideVariableButton}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
}, [isInSubForm, isInSubTable]);
|
||||
}, []);
|
||||
|
||||
const schema = useMemo(() => {
|
||||
return {
|
||||
@ -246,6 +244,13 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
|
||||
width={800}
|
||||
schema={schema}
|
||||
onSubmit={handleSubmit}
|
||||
ModalContextProvider={(props) => {
|
||||
return (
|
||||
<FlagProvider isInSubForm={isInSubForm} isInSubTable={isInSubTable} isInSetDefaultValueDialog>
|
||||
{props.children}
|
||||
</FlagProvider>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
import { Form } from '@formily/core';
|
||||
import { ISchema, Schema } from '@formily/react';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useVariables } from '../../../';
|
||||
import { CollectionFieldOptions_deprecated } from '../../../collection-manager';
|
||||
import { useAPITokenVariable } from './useAPITokenVariable';
|
||||
import { useDatetimeVariable } from './useDateVariable';
|
||||
@ -22,7 +23,6 @@ import { useCurrentRecordVariable } from './useRecordVariable';
|
||||
import { useCurrentRoleVariable } from './useRoleVariable';
|
||||
import { useURLSearchParamsVariable } from './useURLSearchParamsVariable';
|
||||
import { useCurrentUserVariable } from './useUserVariable';
|
||||
import { VariablesContext } from '../../../';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
@ -59,7 +59,7 @@ export const useVariableOptions = ({
|
||||
targetFieldSchema,
|
||||
record,
|
||||
}: Props) => {
|
||||
const { filterVariables = () => true } = useContext(VariablesContext) || {};
|
||||
const { filterVariables = () => true } = useVariables() || {};
|
||||
const blockParentCollectionName = record?.__parent?.__collectionName;
|
||||
const { currentUserSettings } = useCurrentUserVariable({
|
||||
maxDepth: 3,
|
||||
|
@ -53,6 +53,9 @@ const schema: ISchema = {
|
||||
[uid()]: {
|
||||
'x-decorator': 'Form',
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
zIndex: 10000,
|
||||
},
|
||||
type: 'void',
|
||||
title: '{{t("Change password")}}',
|
||||
properties: {
|
||||
|
@ -71,6 +71,9 @@ const schema: ISchema = {
|
||||
useValues: '{{ useCurrentUserValues }}',
|
||||
},
|
||||
'x-component': 'Action.Drawer',
|
||||
'x-component-props': {
|
||||
zIndex: 10000,
|
||||
},
|
||||
type: 'void',
|
||||
title: '{{t("Edit profile")}}',
|
||||
properties: {
|
||||
|
@ -49,6 +49,10 @@ const getFieldPath = (variablePath: string, variablesStore: Record<string, Varia
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Note: There can only be one VariablesProvider in the entire context. It cannot be used in plugins.
|
||||
*/
|
||||
const VariablesProvider = ({ children, filterVariables }: any) => {
|
||||
const ctxRef = useRef<Record<string, any>>({});
|
||||
const api = useAPIClient();
|
||||
|
@ -298,7 +298,11 @@ describe('useVariables', () => {
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(await result.current.parseVariable('{{ $date.today }}').then(({ value }) => typeof value)).toBe('string');
|
||||
expect(
|
||||
await result.current
|
||||
.parseVariable('{{ $date.today }}', [], { fieldOperator: '$dateOn' })
|
||||
.then(({ value }) => typeof value),
|
||||
).toBe('string');
|
||||
expect(
|
||||
Array.isArray(
|
||||
await result.current
|
||||
|
@ -138,10 +138,10 @@ export class OptionsParser {
|
||||
sort = sort.split(',');
|
||||
}
|
||||
|
||||
let defaultSortField = this.model.primaryKeyAttribute;
|
||||
let defaultSortField: Array<string> | string = this.model.primaryKeyAttribute;
|
||||
|
||||
if (Array.isArray(this.collection.filterTargetKey) && this.collection.filterTargetKey.length == 1) {
|
||||
defaultSortField = this.collection.filterTargetKey[0];
|
||||
if (Array.isArray(this.collection.filterTargetKey)) {
|
||||
defaultSortField = this.collection.filterTargetKey;
|
||||
}
|
||||
|
||||
if (!defaultSortField && this.collection.filterTargetKey && !Array.isArray(this.collection.filterTargetKey)) {
|
||||
@ -149,8 +149,11 @@ export class OptionsParser {
|
||||
}
|
||||
|
||||
if (defaultSortField && !this.options?.group) {
|
||||
if (!sort.includes(defaultSortField)) {
|
||||
sort.push(defaultSortField);
|
||||
defaultSortField = lodash.castArray(defaultSortField);
|
||||
for (const key of defaultSortField) {
|
||||
if (!sort.includes(key)) {
|
||||
sort.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ export interface ResourceStorer {
|
||||
getResources(lang: string): Promise<{
|
||||
[ns: string]: Record<string, string>;
|
||||
}>;
|
||||
reset?: () => Promise<void>;
|
||||
}
|
||||
|
||||
export class Locale {
|
||||
@ -41,7 +42,7 @@ export class Locale {
|
||||
this.app.syncMessageManager.subscribe('localeManager', async (message) => {
|
||||
switch (message.type) {
|
||||
case 'reload':
|
||||
await this.cache.reset();
|
||||
await this.reset();
|
||||
return;
|
||||
}
|
||||
});
|
||||
@ -52,14 +53,19 @@ export class Locale {
|
||||
name: 'locale',
|
||||
prefix: 'locale',
|
||||
store: 'memory',
|
||||
max: 2000
|
||||
});
|
||||
|
||||
await this.get(this.defaultLang);
|
||||
}
|
||||
|
||||
async reset() {
|
||||
const storers = Array.from(this.resourceStorers.getValues());
|
||||
const promises = storers.map((storer) => storer.reset());
|
||||
await Promise.all([this.cache.reset(), ...promises]);
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await this.cache.reset();
|
||||
await this.reset();
|
||||
this.app.syncMessageManager.publish('localeManager', { type: 'reload' });
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ const InternalIcons = () => {
|
||||
window.removeEventListener('resize', calculateGap);
|
||||
};
|
||||
}, [Object.keys(fieldSchema?.properties || {}).length]);
|
||||
console.log(gap);
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: designable ? '1rem' : 0 }}>
|
||||
<DndContext>
|
||||
@ -91,7 +91,6 @@ const InternalIcons = () => {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.ant-list-item-meta-title button {
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { expect, test } from '@nocobase/test/e2e';
|
||||
|
||||
test.describe('defaultValue', () => {
|
||||
test('date variables', async ({ page, mockPage }) => {
|
||||
await mockPage().goto();
|
||||
|
||||
// 1. First, create a chart filter block and add a custom date field (date range)
|
||||
await page.getByLabel('schema-initializer-Grid-page:').hover();
|
||||
await page.getByRole('menuitem', { name: 'line-chart Charts' }).click();
|
||||
await page.getByLabel('schema-initializer-Grid-charts:addBlock').hover();
|
||||
await page.getByRole('menuitem', { name: 'Filter' }).click();
|
||||
await page.getByTestId('configure-fields-button-of-chart-filter-item').hover();
|
||||
await page.getByRole('menuitem', { name: 'Custom' }).click();
|
||||
await page.getByLabel('block-item-Input-Field title').getByRole('textbox').fill('date');
|
||||
await page.getByLabel('block-item-Select-Field').getByTestId('select-single').click();
|
||||
await page.getByRole('option', { name: 'Date range' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
// 2. Set a date variable default value for the custom field, after saving, the date input box should display the default value
|
||||
await page.getByLabel('block-item-DatePicker.').hover();
|
||||
await page.getByLabel('designer-schema-settings-DatePicker.RangePicker-ChartFilterItemDesigner').hover();
|
||||
await page.getByRole('menuitem', { name: 'Set default value' }).click();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Date variables right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Last week' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
|
||||
await expect(page.getByPlaceholder('Start date')).toHaveValue(/[0-9]{4}-[0-9]{2}-[0-9]{2}/);
|
||||
await expect(page.getByPlaceholder('End date')).toHaveValue(/[0-9]{4}-[0-9]{2}-[0-9]{2}/);
|
||||
});
|
||||
});
|
@ -27,9 +27,11 @@ describe('actions', () => {
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
process.env.APP_ENV = 'production';
|
||||
app = await createMockServer({
|
||||
plugins: ['localization'],
|
||||
});
|
||||
await app.emitAsync('afterLoad');
|
||||
db = app.db;
|
||||
repo = db.getRepository('localizationTexts');
|
||||
agent = app.agent();
|
||||
@ -106,5 +108,28 @@ describe('actions', () => {
|
||||
expect(res.body.data[0].translation).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('publish', async () => {
|
||||
await repo.create({
|
||||
values: [
|
||||
{
|
||||
module: 'test',
|
||||
text: 'text',
|
||||
translations: [
|
||||
{
|
||||
locale: 'en-US',
|
||||
translation: 'translation',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const { resources } = await app.localeManager.get('en-US');
|
||||
expect(resources.test).toBeUndefined();
|
||||
await agent.resource('localization').publish();
|
||||
const { resources: resources2 } = await app.localeManager.get('en-US');
|
||||
expect(resources2.test).toBeDefined();
|
||||
expect(resources2.test.text).toBe('translation');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -133,6 +133,7 @@ export class PluginLocalizationServer extends Plugin {
|
||||
|
||||
this.app.localeManager.registerResourceStorer('plugin-localization', {
|
||||
getResources: (lang: string) => this.resources.getResources(lang),
|
||||
reset: () => this.resources.reset(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ export default class Resources {
|
||||
await this.cache.set(`texts`, [...existTexts, ...newTexts]);
|
||||
}
|
||||
|
||||
async resetCache(locale: string) {
|
||||
await this.cache.del(`translations:${locale}`);
|
||||
async reset() {
|
||||
await this.cache.reset();
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ const PublicFormQRCode = () => {
|
||||
onOpenChange={handleQRCodeOpen}
|
||||
content={open ? <QRCode value={link} bordered={false} /> : ' '}
|
||||
>
|
||||
{t('QR code', { ns: NAMESPACE })}
|
||||
<a>{t('QR code', { ns: NAMESPACE })}</a>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@ -140,25 +140,22 @@ export function AdminPublicFormPage() {
|
||||
{
|
||||
key: 'enabled',
|
||||
label: (
|
||||
<span
|
||||
<a
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
onClick={() => handleEditPublicForm({ enabled: !enabled })}
|
||||
>
|
||||
<span style={{ marginRight: '10px' }}>{t('Enable form', { ns: NAMESPACE })}</span>
|
||||
<Switch
|
||||
size={'small'}
|
||||
checked={enabled}
|
||||
onChange={(checked) => handleEditPublicForm({ enabled: checked })}
|
||||
/>
|
||||
</span>
|
||||
<Switch size={'small'} checked={enabled} />
|
||||
</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: <span onClick={handleSetPassword}> {t('Set password')}</span>,
|
||||
label: <a onClick={handleSetPassword}> {t('Set password')}</a>,
|
||||
},
|
||||
{
|
||||
key: 'divider1',
|
||||
@ -166,7 +163,7 @@ export function AdminPublicFormPage() {
|
||||
},
|
||||
{
|
||||
key: 'copyLink',
|
||||
label: <span onClick={handleCopyLink}>{t('Copy link')}</span>,
|
||||
label: <a onClick={handleCopyLink}>{t('Copy link')}</a>,
|
||||
},
|
||||
{
|
||||
key: 'qrcode',
|
||||
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Migration } from '@nocobase/server';
|
||||
|
||||
export default class extends Migration {
|
||||
on = 'beforeLoad';
|
||||
appVersion = '<1.5.0-beta';
|
||||
|
||||
async up() {
|
||||
await this.pm.repository.update({
|
||||
values: {
|
||||
builtIn: false,
|
||||
},
|
||||
filter: {
|
||||
name: 'backup-restore',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@ -3,8 +3,15 @@ const fs = require('fs/promises');
|
||||
const path = require('path');
|
||||
const { Command } = require('commander');
|
||||
const program = new Command();
|
||||
const axios = require('axios');
|
||||
|
||||
program.option('-f, --from [from]').option('-t, --to [to]').option('-v, --ver [ver]', '', 'beta').option('--test');
|
||||
program
|
||||
.option('-f, --from [from]')
|
||||
.option('-t, --to [to]')
|
||||
.option('-v, --ver [ver]', '', 'beta')
|
||||
.option('--test')
|
||||
.option('--cmsURL [url]')
|
||||
.option('--cmsToken [token]');
|
||||
program.parse(process.argv);
|
||||
|
||||
const header = {
|
||||
@ -99,7 +106,9 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
|
||||
// gh pr view 5112 --json author,body,files
|
||||
let res;
|
||||
try {
|
||||
const { stdout } = await execa('gh', ['pr', 'view', number, '--json', 'author,body,files,baseRefName'], { cwd });
|
||||
const { stdout } = await execa('gh', ['pr', 'view', number, '--json', 'author,body,files,baseRefName,url'], {
|
||||
cwd,
|
||||
});
|
||||
res = stdout;
|
||||
} catch (error) {
|
||||
console.error(`Get PR #${number} failed, error: ${error.message}`);
|
||||
@ -110,7 +119,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
|
||||
}
|
||||
return { number };
|
||||
}
|
||||
const { author, body, files, baseRefName } = JSON.parse(res);
|
||||
const { author, body, files, baseRefName, url } = JSON.parse(res);
|
||||
if (ver === 'alpha' && baseRefName !== 'next') {
|
||||
return { number };
|
||||
}
|
||||
@ -130,6 +139,7 @@ async function parsePR(number, pkgType, cwd, pkg, retries = 10) {
|
||||
author: author.login,
|
||||
moduleType: name?.includes('plugin-') ? 'plugin' : 'core',
|
||||
module: name,
|
||||
url,
|
||||
en: {
|
||||
module: displayName || pkgName,
|
||||
description,
|
||||
@ -187,17 +197,7 @@ function arrangeChangelogs(changelogs) {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function collect() {
|
||||
let { from, to, ver = 'beta' } = program.opts();
|
||||
if (!from || !to) {
|
||||
// git tag -l --sort=version:refname | grep "v*-ver" | tail -2
|
||||
const tagPattern = `v*-${ver}`;
|
||||
const { stdout: tags } = await execa(`git tag -l --sort=version:refname | grep "${tagPattern}" | tail -2`, {
|
||||
shell: true,
|
||||
});
|
||||
[from, to] = tags.split('\n');
|
||||
}
|
||||
console.log(`From: ${from}, To: ${to}`);
|
||||
async function collect(from, to) {
|
||||
const changelogs = [];
|
||||
const get = async (changelogs, pkgType, cwd, pkg) => {
|
||||
const prs = await getPRList(from, to, cwd);
|
||||
@ -231,11 +231,11 @@ async function collect() {
|
||||
}
|
||||
}
|
||||
}
|
||||
return { changelogs: arrangeChangelogs(changelogs), from, to };
|
||||
return { changelogs: arrangeChangelogs(changelogs) };
|
||||
}
|
||||
|
||||
async function generateChangelog() {
|
||||
const { changelogs, from, to } = await collect();
|
||||
async function generateChangelog(changelogs) {
|
||||
const { test } = program.opts();
|
||||
const prTypeLocale = {
|
||||
'New feature': {
|
||||
en: '🎉 New Features',
|
||||
@ -275,13 +275,13 @@ async function generateChangelog() {
|
||||
const moduleResults = [];
|
||||
const lists = [];
|
||||
for (const changelog of moduleChangelogs) {
|
||||
const { number, author, pro } = changelog;
|
||||
const { number, author, pro, url } = changelog;
|
||||
const { description, docTitle, docLink } = changelog[lang];
|
||||
if (!description) {
|
||||
console.warn(`PR #${number} has no ${lang} changelog`);
|
||||
continue;
|
||||
}
|
||||
const pr = pro ? '' : ` ([#${number}](https://github.com/nocobase/nocobase/pull/${number}))`;
|
||||
const pr = pro && !test ? '' : ` ([#${number}](${url}))`;
|
||||
const doc = docTitle && docLink ? `${referenceLocale[lang]}[${docTitle}](${docLink})` : '';
|
||||
lists.push(`${description}${pr} by @${author}\n${doc}`);
|
||||
}
|
||||
@ -314,7 +314,7 @@ async function generateChangelog() {
|
||||
|
||||
const cn = generate(changelogs, 'cn');
|
||||
const en = generate(changelogs, 'en');
|
||||
return { cn, en, from, to };
|
||||
return { cn, en };
|
||||
}
|
||||
|
||||
async function writeChangelog(cn, en, from, to) {
|
||||
@ -335,30 +335,107 @@ async function writeChangelog(cn, en, from, to) {
|
||||
}
|
||||
|
||||
async function createRelease(cn, en, to) {
|
||||
const { stdout } = await execa('gh', ['release', 'list', '--json', 'tagName']);
|
||||
const releases = JSON.parse(stdout);
|
||||
const tags = releases.map((release) => release.tagName);
|
||||
if (tags.includes(to)) {
|
||||
console.log(`Release ${to} already exists`);
|
||||
return;
|
||||
}
|
||||
let { ver = 'beta' } = program.opts();
|
||||
// gh release create -t title -n note
|
||||
if (ver === 'alpha') {
|
||||
await execa('gh', ['release', 'create', to, '-t', to, '-n', `${en}\n---\n${cn}`, '-p']);
|
||||
await execa('gh', ['release', 'create', to, '-t', to, '-n', en, '-p']);
|
||||
return;
|
||||
}
|
||||
await execa('gh', ['release', 'create', to, '-t', to, '-n', `${en}\n---\n${cn}`]);
|
||||
await execa('gh', ['release', 'create', to, '-t', to, '-n', en]);
|
||||
}
|
||||
|
||||
async function getExistsChangelog(from, to) {
|
||||
const get = async (lang) => {
|
||||
const file = lang === 'cn' ? 'CHANGELOG.zh-CN.md' : 'CHANGELOG.md';
|
||||
const oldChangelog = await fs.readFile(path.join(__dirname, `../../${file}`), 'utf8');
|
||||
if (!oldChangelog.includes(`## [${to}]`)) {
|
||||
return null;
|
||||
}
|
||||
const fromIndex = oldChangelog.indexOf(`## [${from}]`);
|
||||
const toIndex = oldChangelog.indexOf(`## [${to}]`);
|
||||
return oldChangelog.slice(toIndex, fromIndex);
|
||||
};
|
||||
const cn = await get('cn');
|
||||
const en = await get('en');
|
||||
return { cn, en };
|
||||
}
|
||||
|
||||
async function getVersion() {
|
||||
let { from, to, ver = 'beta' } = program.opts();
|
||||
if (!from || !to) {
|
||||
// git tag -l --sort=version:refname | grep "v*-ver" | tail -2
|
||||
const tagPattern = `v*-${ver}`;
|
||||
const { stdout: tags } = await execa(`git tag -l --sort=version:refname | grep "${tagPattern}" | tail -2`, {
|
||||
shell: true,
|
||||
});
|
||||
[from, to] = tags.split('\n');
|
||||
}
|
||||
console.log(`From: ${from}, To: ${to}`);
|
||||
return { from, to };
|
||||
}
|
||||
|
||||
async function postCMS(title, content, contentCN) {
|
||||
const { cmsToken, cmsURL } = program.opts();
|
||||
if (!cmsToken || !cmsURL) {
|
||||
console.error('No cmsToken or cmsURL provided');
|
||||
return;
|
||||
}
|
||||
await axios.request({
|
||||
method: 'post',
|
||||
url: `${cmsURL}/api/articles:updateOrCreate`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${cmsToken}`,
|
||||
},
|
||||
params: {
|
||||
filterKeys: ['title'],
|
||||
},
|
||||
data: {
|
||||
title,
|
||||
title_cn: title,
|
||||
content,
|
||||
content_cn: contentCN,
|
||||
tags: [4],
|
||||
status: 'drafted',
|
||||
author: 'nocobase [bot]',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function writeChangelogAndCreateRelease() {
|
||||
let { ver = 'beta', test } = program.opts();
|
||||
const { cn, en, from, to } = await generateChangelog();
|
||||
if (!cn && !en) {
|
||||
throw new Error('No changelog generated');
|
||||
const { from, to } = await getVersion();
|
||||
let { cn, en } = await getExistsChangelog(from, to);
|
||||
let exists = false;
|
||||
if (cn || en) {
|
||||
exists = true;
|
||||
console.log('Changelog already exists');
|
||||
} else {
|
||||
const { changelogs } = await collect(from, to);
|
||||
const c = await generateChangelog(changelogs);
|
||||
cn = c.cn;
|
||||
en = c.en;
|
||||
if (!cn && !en) {
|
||||
console.error('No changelog generated');
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log(en);
|
||||
console.log(cn);
|
||||
if (test) {
|
||||
console.log(en);
|
||||
console.log(cn);
|
||||
return;
|
||||
}
|
||||
if (ver === 'beta') {
|
||||
if (ver === 'beta' && !exists) {
|
||||
await writeChangelog(cn, en, from, to);
|
||||
}
|
||||
await createRelease(cn, en, to);
|
||||
await postCMS(to, en, cn);
|
||||
}
|
||||
|
||||
writeChangelogAndCreateRelease();
|
||||
|
Loading…
x
Reference in New Issue
Block a user