{
const prefixCls = usePrefixCls('description-textarea', props);
// eslint-disable-next-line react-hooks/rules-of-hooks
const compile = useCompile();
- const value = compile(props.value ?? '');
- const { autop = true, ellipsis } = props;
- const html = (
-
- );
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const content = useMemo(() => {
+ const value = compile(props.value ?? '');
+ const { autop = true, ellipsis } = props;
+ const html = (
+
{
// eslint-disable-next-line react-hooks/rules-of-hooks
const prefixCls = usePrefixCls('json', props);
- const content = props.value != null ? JSON.stringify(props.value, null, props.space ?? 2) : '';
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const content = useMemo(
+ () => (props.value != null ? JSON.stringify(props.value, null, props.space ?? 2) : ''),
+ [props.space, props.value],
+ );
const JSONContent = (
-
+
{content}
);
@@ -260,7 +272,7 @@ ReadPretty.JSON = (props: JSONTextAreaReadPrettyProps) => {
if (props.ellipsis) {
return (
- {content}
+ {content}
);
}
diff --git a/packages/core/client/src/schema-component/antd/list/List.tsx b/packages/core/client/src/schema-component/antd/list/List.tsx
index 53355e9d2c..adbae09b52 100644
--- a/packages/core/client/src/schema-component/antd/list/List.tsx
+++ b/packages/core/client/src/schema-component/antd/list/List.tsx
@@ -9,6 +9,7 @@
import { css, cx } from '@emotion/css';
import { ArrayField } from '@formily/core';
+import { FormLayout } from '@formily/antd-v5';
import { RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { List as AntdList, PaginationProps, theme } from 'antd';
import React, { useCallback, useState } from 'react';
@@ -21,6 +22,7 @@ import { ListDesigner } from './List.Designer';
import { ListItem } from './List.Item';
import useStyles from './List.style';
import { useListActionBarProps, useListBlockHeight } from './hooks';
+import { getCardItemSchema } from '../../../block-provider';
const InternalList = (props) => {
const { service } = useListBlockContext();
@@ -28,6 +30,7 @@ const InternalList = (props) => {
const fieldSchema = useFieldSchema();
const Designer = useDesigner();
const meta = service?.data?.meta;
+ const { pageSize, count, hasNext, page } = meta || {};
const field = useField();
const [schemaMap] = useState(new Map());
const { wrapSSR, componentCls, hashId } = useStyles();
@@ -63,7 +66,58 @@ const InternalList = (props) => {
},
[run, params],
);
-
+ const cardItemSchema = getCardItemSchema?.(fieldSchema);
+ const {
+ layout = 'vertical',
+ labelAlign = 'left',
+ labelWidth = 120,
+ labelWrap = true,
+ } = cardItemSchema?.['x-component-props'] || {};
+ const usePagination = () => {
+ if (!count) {
+ return {
+ onChange: onPaginationChange,
+ total: count || field.value?.length < pageSize || !hasNext ? pageSize * page : pageSize * page + 1,
+ pageSize: pageSize || 10,
+ current: page || 1,
+ showSizeChanger: true,
+ pageSizeOptions,
+ simple: true,
+ className: css`
+ .ant-pagination-simple-pager {
+ display: none !important;
+ }
+ `,
+ itemRender: (_, type, originalElement) => {
+ if (type === 'prev') {
+ return (
+
+ {originalElement}
{page}
+
+ );
+ } else {
+ return originalElement;
+ }
+ },
+ };
+ }
+ return {
+ onChange: onPaginationChange,
+ total: count || 0,
+ pageSize: pageSize || 10,
+ current: page || 1,
+ showSizeChanger: true,
+ pageSizeOptions,
+ };
+ };
+ const paginationProps = usePagination();
return wrapSSR(
{
)}
>
-
- {field.value?.length
- ? field.value.map((item, index) => {
- return (
-
- );
- })
- : null}
-
+
+ {field.value?.length
+ ? field.value.map((item, index) => {
+ return (
+
+ );
+ })
+ : null}
+
+
diff --git a/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx b/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx
index c1171717dc..d36cb00f8e 100644
--- a/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx
+++ b/packages/core/client/src/schema-component/antd/list/__tests__/list.test.tsx
@@ -228,7 +228,9 @@ describe('List', () => {
await waitFor(() => {
expect(screen.queryByText('ID')).toBeInTheDocument();
- expect(screen.queryByText('1')).toBeInTheDocument();
+ const spanElements = screen.getAllByText('1');
+ const firstSpanElement = spanElements.find((el) => el.tagName === 'SPAN');
+ expect(firstSpanElement).toBeInTheDocument();
});
expect(screen.queryByText('Nickname')).toBeInTheDocument();
diff --git a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx
index 93f4ee1009..f840953a82 100644
--- a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx
+++ b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx
@@ -9,25 +9,25 @@
import { observer, useField, useFieldSchema } from '@formily/react';
import { Input as AntdInput, Button, Space, Spin, theme } from 'antd';
+import { TextAreaProps } from 'antd/es/input';
import type { TextAreaRef } from 'antd/es/input/TextArea';
import cls from 'classnames';
-import React, { useCallback, useEffect, useState, useRef } from 'react';
+import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { useGlobalTheme } from '../../../global-theme';
-import { useDesignable } from '../../hooks/useDesignable';
-import { MarkdownVoidDesigner } from './Markdown.Void.Designer';
-import { useStyles } from './style';
-import { TextAreaProps } from 'antd/es/input';
-import { useBlockHeight } from '../../hooks/useBlockSize';
-import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
-import { useCollectionRecord } from '../../../data-source';
-import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
-import { VariableSelect } from '../variable/VariableSelect';
-import { useLocalVariables, useVariables } from '../../../variables';
-import { registerQrcodeWebComponent } from './qrcode-webcom';
-import { getRenderContent } from '../../common/utils/uitls';
-import { parseMarkdown } from './util';
import { useCompile } from '../../';
+import { useCollectionRecord } from '../../../data-source';
+import { useGlobalTheme } from '../../../global-theme';
+import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
+import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
+import { useLocalVariables, useVariables } from '../../../variables';
+import { getRenderContent } from '../../common/utils/uitls';
+import { useBlockHeight } from '../../hooks/useBlockSize';
+import { useDesignable } from '../../hooks/useDesignable';
+import { VariableSelect } from '../variable/VariableSelect';
+import { MarkdownVoidDesigner } from './Markdown.Void.Designer';
+import { registerQrcodeWebComponent } from './qrcode-webcom';
+import { useStyles } from './style';
+import { parseMarkdown } from './util';
export interface MarkdownEditorProps extends Omit {
scope: any[];
defaultValue?: string;
@@ -37,7 +37,7 @@ export interface MarkdownEditorProps extends Omit {
const MarkdownEditor = (props: MarkdownEditorProps) => {
const { scope } = props;
- const { t } = useTranslation();
+ const { t, i18n } = useTranslation();
const [value, setValue] = useState(props.defaultValue);
const inputRef = useRef(null);
const [options, setOptions] = useState([]);
@@ -87,7 +87,12 @@ const MarkdownEditor = (props: MarkdownEditorProps) => {
{t('Syntax references')}:
-
+
+
Handlebars.js
>
@@ -145,15 +150,16 @@ export const MarkdownVoid: any = withDynamicSchemaProps(
useEffect(() => {
setLoading(true);
const cvtContentToHTML = async () => {
- const replacedContent = await getRenderContent(
- engine,
- content,
- compile(variables),
- compile(localVariables),
- parseMarkdown,
- );
-
- setHtml(replacedContent);
+ setTimeout(async () => {
+ const replacedContent = await getRenderContent(
+ engine,
+ content,
+ compile(variables),
+ compile(localVariables),
+ parseMarkdown,
+ );
+ setHtml(replacedContent);
+ });
setLoading(false);
};
cvtContentToHTML();
diff --git a/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx b/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx
index f7237b5e1f..6a3085d08a 100644
--- a/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx
+++ b/packages/core/client/src/schema-component/antd/markdown/Markdown.tsx
@@ -10,7 +10,7 @@
import { LoadingOutlined } from '@ant-design/icons';
import { connect, mapProps, mapReadPretty } from '@formily/react';
import { Input as AntdInput, Spin } from 'antd';
-import React from 'react';
+import React, { useMemo } from 'react';
import { useGlobalTheme } from '../../../global-theme';
import { ReadPretty as InputReadPretty } from '../input';
import { MarkdownVoid } from './Markdown.Void';
@@ -36,16 +36,19 @@ export const MarkdownReadPretty = (props) => {
const { isDarkTheme } = useGlobalTheme();
const { wrapSSR, hashId, componentCls: className } = useStyles({ isDarkTheme });
const { html = '', loading } = useParseMarkdown(props.value);
- const text = convertToText(html);
+ const text = useMemo(() => convertToText(html), [html]);
+
+ if (loading) {
+ return wrapSSR();
+ }
+
const value = (
);
- if (loading) {
- return wrapSSR();
- }
+
return wrapSSR();
};
diff --git a/packages/core/client/src/schema-component/antd/markdown/style.ts b/packages/core/client/src/schema-component/antd/markdown/style.ts
index 0cc9353b4e..a3dae9babb 100644
--- a/packages/core/client/src/schema-component/antd/markdown/style.ts
+++ b/packages/core/client/src/schema-component/antd/markdown/style.ts
@@ -17,6 +17,7 @@ export const useStyles = genStyleHook('nb-markdown', (token, { isDarkTheme }) =>
.toHexShortString();
const defaultStyle: any = {
+ lineHeight: 'inherit',
// default style of markdown
'&.nb-markdown-default': {
'pre code.hljs': { display: 'block', overflowX: 'auto', padding: '1em' },
@@ -43,13 +44,15 @@ export const useStyles = genStyleHook('nb-markdown', (token, { isDarkTheme }) =>
'.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag': {
fontWeight: 700,
},
- '.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type': {
- color: '#800',
- },
+ '.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type':
+ {
+ color: '#800',
+ },
'.hljs-section,.hljs-title': { color: '#800', fontWeight: 700 },
- '.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable': {
- color: '#ab5656',
- },
+ '.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable':
+ {
+ color: '#ab5656',
+ },
'.hljs-literal': { color: '#695' },
'.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code': {
color: '#397300',
@@ -97,15 +100,17 @@ export const useStyles = genStyleHook('nb-markdown', (token, { isDarkTheme }) =>
borderRadius: token.borderRadiusSM,
},
'.hljs': { color: '#adbac7', background: '#22272e' },
- '.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_': {
- color: '#f47067',
- },
+ '.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_':
+ {
+ color: '#f47067',
+ },
'.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_': {
color: '#dcbdfb',
},
- '.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable': {
- color: '#6cb6ff',
- },
+ '.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable':
+ {
+ color: '#6cb6ff',
+ },
'.hljs-meta .hljs-string,.hljs-regexp,.hljs-string': { color: '#96d0ff' },
'.hljs-built_in,.hljs-symbol': { color: '#f69d50' },
'.hljs-code,.hljs-comment,.hljs-formula': { color: '#768390' },
diff --git a/packages/core/client/src/schema-component/antd/markdown/util.ts b/packages/core/client/src/schema-component/antd/markdown/util.ts
index 82eb31701f..1155adee7d 100644
--- a/packages/core/client/src/schema-component/antd/markdown/util.ts
+++ b/packages/core/client/src/schema-component/antd/markdown/util.ts
@@ -7,19 +7,21 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
+import _ from 'lodash';
import { useEffect, useState } from 'react';
-export async function parseMarkdown(text: string) {
+export const parseMarkdown = _.memoize(async (text: string) => {
if (!text) {
return text;
}
const m = await import('./md');
return m.default.render(text);
-}
+});
export function useParseMarkdown(text: string) {
const [html, setHtml] = useState('');
- const [loading, setLoading] = useState(false);
+ const [loading, setLoading] = useState(true);
+
useEffect(() => {
setLoading(true);
parseMarkdown(text)
@@ -29,6 +31,7 @@ export function useParseMarkdown(text: string) {
})
.catch((error) => console.log(error));
}, [text]);
+
return { html, loading };
}
diff --git a/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx b/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx
index 02bc320bae..85d8232bb6 100644
--- a/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx
+++ b/packages/core/client/src/schema-component/antd/menu/Menu.Designer.tsx
@@ -20,6 +20,7 @@ import {
SchemaSettingsModalItem,
SchemaSettingsRemove,
SchemaSettingsSubMenu,
+ SchemaSettingsSwitchItem,
useAPIClient,
useDesignable,
useURLAndHTMLSchema,
@@ -373,6 +374,20 @@ export const MenuDesigner = () => {
initialValues={initialValues}
onSubmit={onEditSubmit}
/>
+ {
+ fieldSchema['x-component-props'].hidden = !!v;
+ field.componentProps.hidden = !!v;
+ dn.emit('patch', {
+ schema: {
+ 'x-uid': fieldSchema['x-uid'],
+ 'x-component-props': fieldSchema['x-component-props'],
+ },
+ });
+ }}
+ />
{
- const s = schema.properties?.[info.key];
+ startTransition(() => {
+ const s = schema.properties?.[info.key];
- if (!s) {
- return;
- }
-
- if (mode === 'mix') {
- if (s['x-component'] !== 'Menu.SubMenu') {
- onSelect?.(info);
- } else {
- const menuItemSchema = findMenuItem(s);
- if (!menuItemSchema) {
- return onSelect?.(info);
- }
- // TODO
- setLoading(true);
- const keys = findKeysByUid(schema, menuItemSchema['x-uid']);
- setDefaultSelectedKeys(keys);
- setTimeout(() => {
- setLoading(false);
- }, 100);
- onSelect?.({
- key: menuItemSchema.name,
- item: {
- props: {
- schema: menuItemSchema,
- },
- },
- });
+ if (!s) {
+ return;
}
- } else {
- onSelect?.(info);
- }
+
+ if (mode === 'mix') {
+ if (s['x-component'] !== 'Menu.SubMenu') {
+ onSelect?.(info);
+ } else {
+ const menuItemSchema = findMenuItem(s);
+ if (!menuItemSchema) {
+ return onSelect?.(info);
+ }
+ // TODO
+ setLoading(true);
+ const keys = findKeysByUid(schema, menuItemSchema['x-uid']);
+ setDefaultSelectedKeys(keys);
+ setTimeout(() => {
+ setLoading(false);
+ }, 100);
+ onSelect?.({
+ key: menuItemSchema.name,
+ item: {
+ props: {
+ schema: menuItemSchema,
+ },
+ },
+ });
+ }
+ } else {
+ onSelect?.(info);
+ }
+ });
},
[schema, mode, onSelect, setLoading, setDefaultSelectedKeys],
);
@@ -301,11 +314,19 @@ const SideMenu = ({
}) => {
const { Component, getMenuItems } = useMenuItem();
- // fix https://nocobase.height.app/T-3331/description
// 使用 ref 用来防止闭包问题
const sideMenuSchemaRef = useRef(sideMenuSchema);
sideMenuSchemaRef.current = sideMenuSchema;
+ const handleSelect = useCallback(
+ (info) => {
+ startTransition(() => {
+ onSelect?.(info);
+ });
+ },
+ [onSelect],
+ );
+
const items = useMemo(() => {
const result = getMenuItems(() => {
return ;
@@ -351,7 +372,7 @@ const SideMenu = ({
mode={'inline'}
openKeys={openKeys}
selectedKeys={selectedKeys}
- onClick={onSelect}
+ onClick={handleSelect}
onOpenChange={setOpenKeys}
className={sideMenuClass}
items={items as MenuProps['items']}
@@ -507,14 +528,16 @@ const menuItemTitleStyle = {
Menu.Item = observer(
(props) => {
const { t } = useMenuTranslation();
+ const { designable } = useDesignable();
const { pushMenuItem } = useCollectMenuItems();
- const { icon, children, ...others } = props;
+ const { icon, children, hidden, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
const item = useMemo(() => {
return {
...others,
+ hidden: designable ? false : hidden,
className: menuItemClass,
key: schema.name,
eventKey: schema.name,
@@ -599,7 +622,8 @@ const MenuURLButton = ({ href, params, icon }) => {
Menu.URL = observer(
(props) => {
const { pushMenuItem } = useCollectMenuItems();
- const { icon, children, ...others } = props;
+ const { designable } = useDesignable();
+ const { icon, children, hidden, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
@@ -612,6 +636,7 @@ Menu.URL = observer(
const item = useMemo(() => {
return {
...others,
+ hidden: designable ? false : hidden,
className: menuItemClass,
key: schema.name,
eventKey: schema.name,
@@ -625,7 +650,7 @@ Menu.URL = observer(
),
};
- }, [field.title, icon, props.href, schema, JSON.stringify(props.params)]);
+ }, [field.title, designable, hidden, icon, props.href, schema, JSON.stringify(props.params)]);
pushMenuItem(item);
return null;
@@ -636,9 +661,10 @@ Menu.URL = observer(
Menu.SubMenu = observer(
(props) => {
const { t } = useMenuTranslation();
+ const { designable } = useDesignable();
const { Component, getMenuItems } = useMenuItem();
const { pushMenuItem } = useCollectMenuItems();
- const { icon, children, ...others } = props;
+ const { icon, children, hidden, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const mode = useContext(MenuModeContext);
@@ -646,6 +672,7 @@ Menu.SubMenu = observer(
const submenu = useMemo(() => {
return {
...others,
+ hidden: designable ? false : hidden,
className: menuItemClass,
key: schema.name,
eventKey: schema.name,
diff --git a/packages/core/client/src/schema-component/antd/page/index.ts b/packages/core/client/src/schema-component/antd/page/index.ts
index ed22ee7a07..7f8883bfdb 100644
--- a/packages/core/client/src/schema-component/antd/page/index.ts
+++ b/packages/core/client/src/schema-component/antd/page/index.ts
@@ -12,7 +12,7 @@ export * from './FixedBlock';
export * from './FixedBlockDesignerItem';
export * from './Page';
export * from './Page.Settings';
-export { PagePopups } from './PagePopups';
+export { PagePopups, useCurrentPopupContext } from './PagePopups';
export { getPopupPathFromParams, getStoredPopupContext, storePopupContext, withSearchParams } from './pagePopupUtils';
export * from './PageTab.Settings';
export { PopupSettingsProvider, usePopupSettings } from './PopupSettingsProvider';
diff --git a/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx b/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx
index 1f34e81afa..3af5690adb 100644
--- a/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx
+++ b/packages/core/client/src/schema-component/antd/page/pagePopupUtils.tsx
@@ -145,7 +145,7 @@ export const usePopupUtils = (
const collection = useCollection();
const cm = useCollectionManager();
const association = useAssociationName();
- const { visible, setVisible } = useContext(PopupVisibleProviderContext) || { visible: false, setVisible: () => {} };
+ const { visible, setVisible } = useContext(PopupVisibleProviderContext) || { visible: false, setVisible: _.noop };
const { params: popupParams } = useCurrentPopupContext();
const service = useDataBlockRequest();
const { isPopupVisibleControlledByURL } = usePopupSettings();
diff --git a/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx b/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx
index 6f711c2395..5250a1b63d 100644
--- a/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx
+++ b/packages/core/client/src/schema-component/antd/pagination/__tests__/pagination.test.tsx
@@ -21,12 +21,12 @@ describe('Pagination', () => {
expect(container).toMatchInlineSnapshot(`