mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
Merge branch 'next' into develop
This commit is contained in:
commit
8d89174f31
12
packages/core/client/src/i18n/constant.ts
Normal file
12
packages/core/client/src/i18n/constant.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
const NAMESPACE_UI_SCHEMA = 'ui-schema-storage';
|
||||
|
||||
export { NAMESPACE_UI_SCHEMA };
|
@ -8,3 +8,4 @@
|
||||
*/
|
||||
|
||||
export * from './i18n';
|
||||
export * from './constant';
|
||||
|
@ -37,8 +37,8 @@ test.describe('linkage rules', () => {
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
|
||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').click();
|
||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').fill('123');
|
||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByRole('textbox').click();
|
||||
await page.getByLabel('Linkage rules').getByRole('tabpanel').getByRole('textbox').fill('123');
|
||||
|
||||
// action:禁用 longText 字段
|
||||
await page.getByText('Add property').click();
|
||||
|
@ -85,6 +85,7 @@ test.describe('configure fields', () => {
|
||||
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
||||
await page.mouse.move(600, 0);
|
||||
await page.reload();
|
||||
|
||||
await expect(page.getByLabel('block-item-CollectionField-general-form-general.manyToOne1-manyToOne1')).toHaveText(
|
||||
`manyToOne1:${record.manyToOne1.id}`,
|
||||
|
@ -41,6 +41,7 @@ test.describe('where list block can be added', () => {
|
||||
await page.getByLabel('schema-initializer-Grid-').nth(1).hover();
|
||||
await page.getByRole('menuitem', { name: 'Role name' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Root')).toBeVisible();
|
||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Admin')).toBeVisible();
|
||||
await expect(page.getByLabel('block-item-CollectionField-').getByText('Member')).toBeVisible();
|
||||
|
@ -13,14 +13,16 @@ import { T4334 } from '../templatesOfBug';
|
||||
// fix https://nocobase.height.app/T-2187
|
||||
test('action linkage by row data', async ({ page, mockPage }) => {
|
||||
await mockPage(T4334).goto();
|
||||
const adminEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-admin');
|
||||
const adminEditAction = page
|
||||
.getByLabel('action-Action.Link-Edit-update-roles-table-admin')
|
||||
.locator('.nb-action-title');
|
||||
const adminEditActionStyle = await adminEditAction.evaluate((element) => {
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
return {
|
||||
opacity: computedStyle.opacity,
|
||||
};
|
||||
});
|
||||
const rootEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-root');
|
||||
const rootEditAction = page.getByLabel('action-Action.Link-Edit-update-roles-table-root').locator('.nb-action-title');
|
||||
const rootEditActionStyle = await rootEditAction.evaluate((element) => {
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
return {
|
||||
|
@ -156,6 +156,7 @@ test.describe('configure columns', () => {
|
||||
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
||||
await page.mouse.move(600, 0);
|
||||
await page.reload();
|
||||
|
||||
// 2. Click on the association field, create a details block in the popup, display the ID field, and assert if it's correct
|
||||
await page
|
||||
@ -194,6 +195,7 @@ test.describe('configure columns', () => {
|
||||
await page.getByLabel('schema-initializer-Grid-details:configureFields-emptyCollection').hover();
|
||||
await page.getByRole('menuitem', { name: 'ID', exact: true }).click();
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await expect(page.getByLabel('block-item-CollectionField-')).toHaveText(
|
||||
`ID:${record.manyToOne1.manyToOne2.manyToOne3.id}`,
|
||||
);
|
||||
@ -212,6 +214,7 @@ test.describe('configure columns', () => {
|
||||
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
||||
await page.mouse.move(600, 0);
|
||||
await page.reload();
|
||||
|
||||
// 2. 点击每一个关系字段,创建一个详情区块,显示 ID 字段,断言 ID 是否正确
|
||||
await page
|
||||
@ -307,8 +310,8 @@ test.describe('configure actions column', () => {
|
||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
|
||||
await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||
// await page.getByText('Actions', { exact: true }).hover({ force: true });
|
||||
// await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
|
||||
await page.mouse.move(300, 0);
|
||||
|
@ -36,7 +36,8 @@ test.describe('popup router', () => {
|
||||
await page.locator('.ant-drawer-mask').click();
|
||||
|
||||
// expect to be back to the first page
|
||||
await page.getByText('Users单层子页面Configure').hover();
|
||||
await page.getByLabel('block-item-CardItem-users-').getByText('Users 单层子页面Configure').hover();
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
||||
).toBeVisible();
|
||||
@ -60,7 +61,7 @@ test.describe('popup router', () => {
|
||||
await page.locator('.ant-drawer-mask').click();
|
||||
|
||||
// expect to be back to the first page
|
||||
await page.getByText('Users单层子页面Configure').hover();
|
||||
await page.getByLabel('block-item-CardItem-users-').getByText('Users 单层子页面Configure').hover();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'designer-schema-settings-CardItem-blockSettings:table-users' }),
|
||||
).toBeVisible();
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||
import { Drawer } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { FC, startTransition, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
||||
@ -22,6 +23,7 @@ import { useActionContext } from './hooks';
|
||||
import { useSetAriaLabelForDrawer } from './hooks/useSetAriaLabelForDrawer';
|
||||
import { ActionDrawerProps, ComposedActionDrawer, OpenSize } from './types';
|
||||
import { getZIndex, useZIndexContext, zIndexContext } from './zIndexContext';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
const MemoizeRecursionField = React.memo(RecursionField);
|
||||
MemoizeRecursionField.displayName = 'MemoizeRecursionField';
|
||||
@ -81,6 +83,7 @@ export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
|
||||
const { visible, setVisible, openSize = 'middle', drawerProps } = useActionContext();
|
||||
const schema = useFieldSchema();
|
||||
const field = useField();
|
||||
const { t } = useTranslation();
|
||||
const { componentCls, hashId } = useStyles();
|
||||
const tabContext = useTabsContext();
|
||||
const parentZIndex = useZIndexContext();
|
||||
@ -126,7 +129,7 @@ export const InternalActionDrawer: React.FC<ActionDrawerProps> = observer(
|
||||
<Drawer
|
||||
zIndex={zIndex}
|
||||
width={openSizeWidthMap.get(openSize)}
|
||||
title={field.title}
|
||||
title={t(field.title, { ns: NAMESPACE_UI_SCHEMA })}
|
||||
{...others}
|
||||
{...drawerProps}
|
||||
rootStyle={rootStyle}
|
||||
|
@ -48,6 +48,7 @@ import { ActionContextProvider } from './context';
|
||||
import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction';
|
||||
import { ActionContextProps, ActionProps, ComposedAction } from './types';
|
||||
import { linkageAction, setInitialActionState } from './utils';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
// 这个要放到最下面,否则会导致前端单测失败
|
||||
import { useApp } from '../../../application';
|
||||
@ -563,6 +564,7 @@ const RenderButtonInner = observer(
|
||||
onlyIcon,
|
||||
...others
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
const debouncedClick = useCallback(
|
||||
debounce(
|
||||
(e: React.MouseEvent, checkPortal = true) => {
|
||||
@ -584,7 +586,8 @@ const RenderButtonInner = observer(
|
||||
return null;
|
||||
}
|
||||
|
||||
const actionTitle = title || field?.title;
|
||||
const rawTitle = title ?? field?.title;
|
||||
const actionTitle = typeof rawTitle === 'string' ? t(rawTitle, { ns: NAMESPACE_UI_SCHEMA }) : rawTitle;
|
||||
const { opacity, ...restButtonStyle } = buttonStyle;
|
||||
const linkStyle = isLink && opacity ? { opacity } : undefined;
|
||||
return (
|
||||
|
@ -9,8 +9,10 @@
|
||||
|
||||
import { Card, CardProps } from 'antd';
|
||||
import React, { useMemo, useRef, useEffect, createContext, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToken } from '../../../style';
|
||||
import { MarkdownReadPretty } from '../markdown';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
export const BlockItemCardContext = createContext({});
|
||||
|
||||
@ -22,6 +24,7 @@ export const BlockItemCard = React.forwardRef<HTMLDivElement, CardProps | any>((
|
||||
}, [token.marginBlock]);
|
||||
const [titleHeight, setTitleHeight] = useState(0);
|
||||
const titleRef = useRef<HTMLDivElement | null>(null);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (titleRef.current) {
|
||||
@ -38,10 +41,10 @@ export const BlockItemCard = React.forwardRef<HTMLDivElement, CardProps | any>((
|
||||
}, [blockTitle, description]);
|
||||
const title = (blockTitle || description) && (
|
||||
<div ref={titleRef} style={{ padding: '8px 0px 8px' }}>
|
||||
<span>{blockTitle}</span>
|
||||
<span> {t(blockTitle, { ns: NAMESPACE_UI_SCHEMA })}</span>
|
||||
{description && (
|
||||
<MarkdownReadPretty
|
||||
value={props.description}
|
||||
value={t(description, { ns: NAMESPACE_UI_SCHEMA })}
|
||||
style={{
|
||||
overflowWrap: 'break-word',
|
||||
whiteSpace: 'normal',
|
||||
|
@ -27,6 +27,8 @@ import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
||||
import useLazyLoadDisplayAssociationFieldsOfForm from './hooks/useLazyLoadDisplayAssociationFieldsOfForm';
|
||||
import { useLinkageRulesForSubTableOrSubForm } from './hooks/useLinkageRulesForSubTableOrSubForm';
|
||||
import useParseDefaultValue from './hooks/useParseDefaultValue';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
Item.displayName = 'FormilyFormItem';
|
||||
|
||||
@ -64,7 +66,7 @@ export const FormItem: any = withDynamicSchemaProps(
|
||||
const schema = useFieldSchema();
|
||||
const { addActiveFieldName } = useFormActiveFields() || {};
|
||||
const { wrapperStyle }: { wrapperStyle: any } = useDataFormItemProps();
|
||||
|
||||
const { t } = useTranslation();
|
||||
useParseDefaultValue();
|
||||
useLazyLoadDisplayAssociationFieldsOfForm();
|
||||
useLinkageRulesForSubTableOrSubForm();
|
||||
@ -72,18 +74,20 @@ export const FormItem: any = withDynamicSchemaProps(
|
||||
useEffect(() => {
|
||||
addActiveFieldName?.(schema.name as string);
|
||||
}, [addActiveFieldName, schema.name]);
|
||||
|
||||
field.title = t(field.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||
const showTitle = schema['x-decorator-props']?.showTitle ?? true;
|
||||
const extra = useMemo(() => {
|
||||
if (field.description && field.description !== '') {
|
||||
return typeof field.description === 'string' ? (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: HTMLEncode(field.description).split('\n').join('<br/>'),
|
||||
__html: HTMLEncode(t(field.description, { ns: NAMESPACE_UI_SCHEMA }))
|
||||
.split('\n')
|
||||
.join('<br/>'),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
field.description
|
||||
t(field.description, { ns: NAMESPACE_UI_SCHEMA })
|
||||
);
|
||||
}
|
||||
}, [field.description]);
|
||||
|
@ -52,6 +52,7 @@ import { useStyles } from './Page.style';
|
||||
import { PageDesigner, PageTabDesigner } from './PageTabDesigner';
|
||||
import { PopupRouteContextResetter } from './PopupRouteContextResetter';
|
||||
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
interface PageProps {
|
||||
currentTabUid: string;
|
||||
@ -431,7 +432,8 @@ const NocoBasePageHeader = React.memo(({ activeKey, className }: { activeKey: st
|
||||
const { token } = useToken();
|
||||
|
||||
useEffect(() => {
|
||||
const title = t(fieldSchema.title) || t(currentRoute?.title);
|
||||
const title =
|
||||
t(fieldSchema.title, { ns: NAMESPACE_UI_SCHEMA }) || t(currentRoute?.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||
if (title) {
|
||||
setDocumentTitle(title);
|
||||
setPageTitle(title);
|
||||
|
@ -9,8 +9,11 @@
|
||||
|
||||
import { useField } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
export const TableColumn = (props) => {
|
||||
const field = useField();
|
||||
return <div role="button">{field.title}</div>;
|
||||
const { t } = useTranslation();
|
||||
return <div role="button">{t(field.title, { ns: NAMESPACE_UI_SCHEMA })}</div>;
|
||||
};
|
||||
|
@ -67,6 +67,7 @@ import { useAssociationFieldContext } from '../association-field/hooks';
|
||||
import { TableSkeleton } from './TableSkeleton';
|
||||
import { extractIndex, isCollectionFieldComponent, isColumnComponent } from './utils';
|
||||
import { withTooltipComponent } from '../../../hoc/withTooltipComponent';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
type BodyRowComponentProps = {
|
||||
rowIndex?: number;
|
||||
@ -164,6 +165,7 @@ const useTableColumns = (
|
||||
props: { showDel?: any; isSubTable?: boolean; optimizeTextCellRender: boolean },
|
||||
paginationProps,
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const { token } = useToken();
|
||||
const field = useArrayField(props);
|
||||
const schema = useFieldSchema();
|
||||
@ -213,11 +215,10 @@ const useTableColumns = (
|
||||
const dataIndex = collectionFields?.length > 0 ? collectionFields[0].name : columnSchema.name;
|
||||
const columnHidden = !!columnSchema['x-component-props']?.['columnHidden'];
|
||||
const { uiSchema, defaultValue, interface: _interface } = collection?.getField(dataIndex) || {};
|
||||
|
||||
columnSchema.title = t(columnSchema?.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||
if (uiSchema) {
|
||||
uiSchema.default = defaultValue;
|
||||
}
|
||||
|
||||
return {
|
||||
title: (
|
||||
<RefreshComponentProvider refresh={refresh}>
|
||||
|
@ -12,6 +12,7 @@ import { observer, RecursionField, Schema, useField, useFieldSchema } from '@for
|
||||
import { Tabs as AntdTabs, TabPaneProps, TabsProps } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSchemaInitializerRender } from '../../../application';
|
||||
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
||||
import { Icon } from '../../../icon';
|
||||
@ -22,6 +23,7 @@ import { useTabsContext } from './context';
|
||||
import { TabsDesigner } from './Tabs.Designer';
|
||||
import { useMobileLayout } from '../../../route-switch/antd/admin-layout';
|
||||
import { transformMultiColumnToSingleColumn } from '@nocobase/utils/client';
|
||||
import { NAMESPACE_UI_SCHEMA } from '../../../i18n/constant';
|
||||
|
||||
const MemoizeRecursionField = React.memo(RecursionField);
|
||||
MemoizeRecursionField.displayName = 'MemoizeRecursionField';
|
||||
@ -136,14 +138,15 @@ Tabs.TabPane = withDynamicSchemaProps(
|
||||
(props: TabPaneProps & { icon?: any; hidden?: boolean }) => {
|
||||
const Designer = useDesigner();
|
||||
const field = useField();
|
||||
|
||||
const { t } = useTranslation();
|
||||
if (props.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem className={classNames('nb-action-link', designerCss, props.className)}>
|
||||
{props.icon && <Icon style={{ marginRight: 2 }} type={props.icon} />} {props.tab || field.title}
|
||||
{props.icon && <Icon style={{ marginRight: 2 }} type={props.icon} />}{' '}
|
||||
{props.tab || t(field.title, { ns: NAMESPACE_UI_SCHEMA })}
|
||||
<Designer />
|
||||
</SortableItem>
|
||||
);
|
||||
|
@ -37,8 +37,8 @@ test.describe('data will be updated && Assign field values && after successful s
|
||||
await page.getByRole('menuitem', { name: 'After successful submission' }).click();
|
||||
await page.getByLabel('Manually close').check();
|
||||
await page.getByLabel('Redirect to').check();
|
||||
await page.locator('input[type="text"]').click();
|
||||
await page.locator('input[type="text"]').fill('/admin/pm/list/local/');
|
||||
await page.getByLabel('textbox').click();
|
||||
await page.getByLabel('textbox').fill('/admin/pm/list/local/');
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
await page.getByLabel('action-Action-Bulk update-customize:bulkUpdate-general-table').click();
|
||||
const [request] = await Promise.all([
|
||||
|
@ -25,9 +25,7 @@ test.describe('custom request action', () => {
|
||||
await page.getByLabel('designer-schema-settings-CustomRequestAction-actionSettings:customRequest-').hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit button' }).click();
|
||||
|
||||
// 应该只显示标题输入框
|
||||
await expect(page.getByText('Button title')).toBeVisible();
|
||||
await expect(page.getByText('Button icon')).not.toBeVisible();
|
||||
await expect(page.getByText('Button background color')).not.toBeVisible();
|
||||
});
|
||||
|
||||
|
@ -8,10 +8,18 @@
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { Action, Icon, useCompile, useComponent, withDynamicSchemaProps, ACLActionProvider } from '@nocobase/client';
|
||||
import {
|
||||
Action,
|
||||
Icon,
|
||||
useComponent,
|
||||
withDynamicSchemaProps,
|
||||
ACLActionProvider,
|
||||
NAMESPACE_UI_SCHEMA,
|
||||
} from '@nocobase/client';
|
||||
import { Avatar } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React, { useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { WorkbenchBlockContext } from './WorkbenchBlock';
|
||||
import { WorkbenchLayout } from './workbenchBlockSettings';
|
||||
|
||||
@ -40,8 +48,8 @@ function Button() {
|
||||
const backgroundColor = fieldSchema['x-component-props']?.['iconColor'];
|
||||
const { layout, ellipsis = true } = useContext(WorkbenchBlockContext);
|
||||
const { styles, cx } = useStyles();
|
||||
const compile = useCompile();
|
||||
const title = compile(fieldSchema.title);
|
||||
const { t } = useTranslation();
|
||||
const title = t(fieldSchema.title, { ns: NAMESPACE_UI_SCHEMA });
|
||||
return layout === WorkbenchLayout.Grid ? (
|
||||
<div title={title} className={cx(styles.avatar)}>
|
||||
<Avatar style={{ backgroundColor }} size={48} icon={<Icon type={icon} />} />
|
||||
|
@ -10,7 +10,7 @@
|
||||
import { Cache } from '@nocobase/cache';
|
||||
import { Repository, Transaction, Transactionable } from '@nocobase/database';
|
||||
import { uid } from '@nocobase/utils';
|
||||
import lodash from 'lodash';
|
||||
import { default as _, default as lodash } from 'lodash';
|
||||
import { ChildOptions, SchemaNode, TargetPosition } from './dao/ui_schema_node_dao';
|
||||
|
||||
export interface GetJsonSchemaOptions {
|
||||
@ -297,6 +297,23 @@ export class UiSchemaRepository extends Repository {
|
||||
);
|
||||
}
|
||||
|
||||
async emitAfterSaveEvent(s, options) {
|
||||
if (!s?.schema) {
|
||||
return;
|
||||
}
|
||||
const keys = ['title', 'description', 'x-component-props.title', 'x-decorator-props.title'];
|
||||
let r = false;
|
||||
for (const key of keys) {
|
||||
if (_.get(s?.schema, key)) {
|
||||
r = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r) {
|
||||
await this.database.emitAsync(`${this.collection.name}.afterSave`, s, options);
|
||||
}
|
||||
}
|
||||
|
||||
@transaction()
|
||||
async patch(newSchema: any, options?) {
|
||||
const { transaction } = options;
|
||||
@ -305,8 +322,8 @@ export class UiSchemaRepository extends Repository {
|
||||
if (!newSchema['properties']) {
|
||||
const s = await this.model.findByPk(rootUid, { transaction });
|
||||
s.set('schema', { ...s.toJSON(), ...newSchema });
|
||||
// console.log(s.toJSON());
|
||||
await s.save({ transaction, hooks: false });
|
||||
await this.emitAfterSaveEvent(s, options);
|
||||
if (newSchema['x-server-hooks']) {
|
||||
await this.database.emitAsync(`${this.collection.name}.afterSave`, s, options);
|
||||
}
|
||||
@ -488,8 +505,14 @@ export class UiSchemaRepository extends Repository {
|
||||
}
|
||||
|
||||
const result = await this[`insert${lodash.upperFirst(position)}`](target, schema, options);
|
||||
|
||||
const s = await this.model.findByPk(schema, { transaction });
|
||||
|
||||
await this.emitAfterSaveEvent(s, options);
|
||||
|
||||
// clear target schema path cache
|
||||
await this.clearXUidPathCache(result['x-uid'], transaction);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -869,6 +892,8 @@ WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and TreeTable.sort
|
||||
},
|
||||
);
|
||||
|
||||
await this.emitAfterSaveEvent(nodeModel, { transaction });
|
||||
|
||||
if (schema['x-server-hooks']) {
|
||||
await this.database.emitAsync(`${this.collection.name}.afterSave`, nodeModel, { transaction });
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
import { MagicAttributeModel } from '@nocobase/database';
|
||||
import { Plugin } from '@nocobase/server';
|
||||
import PluginLocalizationServer from '@nocobase/plugin-localization';
|
||||
import { tval } from '@nocobase/utils';
|
||||
import { uid } from '@nocobase/utils';
|
||||
import path, { resolve } from 'path';
|
||||
import { uiSchemaActions } from './actions/ui-schema-action';
|
||||
@ -17,6 +19,19 @@ import UiSchemaRepository from './repository';
|
||||
import { ServerHooks } from './server-hooks';
|
||||
import { ServerHookModel } from './server-hooks/model';
|
||||
|
||||
export const compile = (title: string) => (title || '').replace(/{{\s*t\(["|'|`](.*)["|'|`]\)\s*}}/g, '$1');
|
||||
|
||||
function extractFields(obj) {
|
||||
return [
|
||||
obj.title,
|
||||
obj.description,
|
||||
obj['x-component-props']?.title,
|
||||
obj['x-component-props']?.description,
|
||||
obj['x-decorator-props']?.title,
|
||||
obj['x-decorator-props']?.description,
|
||||
].filter((value) => value !== undefined && value !== '');
|
||||
}
|
||||
|
||||
export class PluginUISchemaStorageServer extends Plugin {
|
||||
serverHooks: ServerHooks;
|
||||
|
||||
@ -28,7 +43,7 @@ export class PluginUISchemaStorageServer extends Plugin {
|
||||
|
||||
async beforeLoad() {
|
||||
const db = this.app.db;
|
||||
|
||||
const pm = this.app.pm;
|
||||
this.serverHooks = new ServerHooks(db);
|
||||
|
||||
this.app.db.registerModels({ MagicAttributeModel, UiSchemaModel, ServerHookModel });
|
||||
@ -51,6 +66,19 @@ export class PluginUISchemaStorageServer extends Plugin {
|
||||
}
|
||||
});
|
||||
|
||||
db.on('uiSchemas.afterSave', async function setUid(model, options) {
|
||||
const localizationPlugin = pm.get('localization') as PluginLocalizationServer;
|
||||
const texts = [];
|
||||
const changedFields = extractFields(model.toJSON());
|
||||
if (!changedFields.length) {
|
||||
return;
|
||||
}
|
||||
changedFields.forEach((field) => {
|
||||
field && texts.push({ text: compile(field), module: `resources.ui-schema-storage` });
|
||||
});
|
||||
await localizationPlugin?.addNewTexts?.(texts, options);
|
||||
});
|
||||
|
||||
db.on('uiSchemas.afterCreate', async function insertSchema(model, options) {
|
||||
const { transaction } = options;
|
||||
const uiSchemaRepository = db.getCollection('uiSchemas').repository as UiSchemaRepository;
|
||||
@ -125,6 +153,34 @@ export class PluginUISchemaStorageServer extends Plugin {
|
||||
]);
|
||||
|
||||
await this.importCollections(resolve(__dirname, 'collections'));
|
||||
// this.registerLocalizationSource();
|
||||
}
|
||||
|
||||
registerLocalizationSource() {
|
||||
const localizationPlugin = this.app.pm.get('localization') as PluginLocalizationServer;
|
||||
if (!localizationPlugin) {
|
||||
return;
|
||||
}
|
||||
localizationPlugin.sourceManager.registerSource('ui-schema-storage', {
|
||||
title: tval('UiSchema'),
|
||||
sync: async (ctx) => {
|
||||
const uiSchemas = await ctx.db.getRepository('uiSchemas').find({
|
||||
raw: true,
|
||||
});
|
||||
const resources = {};
|
||||
uiSchemas.forEach((route: { schema?: any }) => {
|
||||
const changedFields = extractFields(route.schema);
|
||||
if (changedFields.length) {
|
||||
changedFields.forEach((field) => {
|
||||
resources[field] = '';
|
||||
});
|
||||
}
|
||||
});
|
||||
return {
|
||||
'ui-schema-storage': resources,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user