Merge branch 'main' into next

This commit is contained in:
Zeke Zhang 2024-07-10 12:42:24 +08:00
commit 937209363a
11 changed files with 327 additions and 279 deletions

View File

@ -1529,14 +1529,36 @@ export function appendQueryStringToUrl(url: string, queryString: string) {
return url;
}
export const useParseURLAndParams = () => {
const variables = useVariables();
const localVariables = useLocalVariables();
const parseURLAndParams = useCallback(
async (url: string, params: { name: string; value: any }[]) => {
const queryString = await parseVariablesAndChangeParamsToQueryString({
searchParams: params,
variables,
localVariables,
replaceVariableValue,
});
const targetUrl = await replaceVariableValue(url, variables, localVariables);
const result = appendQueryStringToUrl(targetUrl, queryString);
return result;
},
[variables, localVariables],
);
return { parseURLAndParams };
};
export function useLinkActionProps() {
const navigate = useNavigateNoUpdate();
const fieldSchema = useFieldSchema();
const { t } = useTranslation();
const url = fieldSchema?.['x-component-props']?.['url'];
const searchParams = fieldSchema?.['x-component-props']?.['params'] || [];
const variables = useVariables();
const localVariables = useLocalVariables();
const { parseURLAndParams } = useParseURLAndParams();
return {
type: 'default',
@ -1545,14 +1567,8 @@ export function useLinkActionProps() {
message.warning(t('Please configure the URL'));
return;
}
const queryString = await parseVariablesAndChangeParamsToQueryString({
searchParams,
variables,
localVariables,
replaceVariableValue,
});
const targetUrl = await replaceVariableValue(url, variables, localVariables);
const link = appendQueryStringToUrl(targetUrl, queryString);
const link = await parseURLAndParams(url, searchParams);
if (link) {
if (isURL(link)) {
window.open(link, '_blank');

View File

@ -63,6 +63,7 @@ export * from './variables';
export { withDynamicSchemaProps } from './hoc/withDynamicSchemaProps';
export { SchemaSettingsActionLinkItem } from './modules/actions/link/customizeLinkActionSettings';
export { useURLAndParamsSchema } from './modules/actions/link/useURLAndParamsSchema';
export * from './modules/blocks/BlockSchemaToolbar';
export * from './modules/blocks/data-blocks/form';
export * from './modules/blocks/data-blocks/table';

View File

@ -26,7 +26,7 @@ test.describe('Link', () => {
await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover();
await page.getByRole('menuitem', { name: 'Edit link' }).click();
await page
.getByLabel('block-item-Variable.TextArea-users-table-URL')
.getByLabel('block-item-users-table-URL')
.getByLabel('textbox')
.fill(await nocoPage.getUrl());
await page.getByPlaceholder('Name').fill('id');

View File

@ -6,34 +6,27 @@
* 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 { css } from '@emotion/css';
import { ArrayItems } from '@formily/antd-v5';
import { useField, useFieldSchema } from '@formily/react';
import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useCollectionRecord, useDesignable, useFormBlockContext, useRecord } from '../../../';
import { useCollectionRecord, useDesignable } from '../../../';
import { useSchemaToolbar } from '../../../application';
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
import { useCollection_deprecated } from '../../../collection-manager';
import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer';
import { SchemaSettingsLinkageRules, SchemaSettingsModalItem } from '../../../schema-settings';
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
import { useURLAndParamsSchema } from './useURLAndParamsSchema';
export function SchemaSettingsActionLinkItem() {
const field = useField();
const fieldSchema = useFieldSchema();
const { dn } = useDesignable();
const { t } = useTranslation();
const { form } = useFormBlockContext();
const record = useRecord();
const scope = useVariableOptions({
collectionField: { uiSchema: fieldSchema },
form,
record,
uiSchema: fieldSchema,
noDisabled: true,
});
const { urlSchema, paramsSchema } = useURLAndParamsSchema();
const initialValues = { url: field.componentProps.url, params: field.componentProps.params || [{}] };
return (
<SchemaSettingsModalItem
title={t('Edit link')}
@ -43,78 +36,10 @@ export function SchemaSettingsActionLinkItem() {
title: t('Edit link'),
properties: {
url: {
title: t('URL'),
type: 'string',
default: fieldSchema?.['x-component-props']?.['url'],
'x-decorator': 'FormItem',
'x-component': 'Variable.TextArea',
'x-component-props': {
scope,
changeOnSelect: true,
},
description: t('Do not concatenate search params in the URL'),
},
params: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: `{{t("Search parameters")}}`,
default: fieldSchema?.['x-component-props']?.params || [{}],
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
style: {
flexWrap: 'nowrap',
maxWidth: '100%',
},
className: css`
& > .ant-space-item:first-child,
& > .ant-space-item:last-child {
flex-shrink: 0;
}
`,
},
properties: {
name: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: `{{t("Name")}}`,
},
},
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Variable.TextArea',
'x-component-props': {
scope,
placeholder: `{{t("Value")}}`,
useTypedConstant: true,
changeOnSelect: true,
},
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
properties: {
add: {
type: 'void',
title: `{{t("Add parameter")}}`,
'x-component': 'ArrayItems.Addition',
},
},
...urlSchema,
required: true,
},
params: paramsSchema,
},
}}
onSubmit={({ url, params }) => {
@ -133,6 +58,7 @@ export function SchemaSettingsActionLinkItem() {
});
dn.refresh();
}}
initialValues={initialValues}
/>
);
}

View File

@ -0,0 +1,129 @@
/**
* 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 { css } from '@emotion/css';
import { useFieldSchema } from '@formily/react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
import { useRecord } from '../../../record-provider';
import { Variable } from '../../../schema-component/antd/variable/Variable';
import { useVariableOptions } from '../../../schema-settings/VariableInput/hooks/useVariableOptions';
const getVariableComponentWithScope = (Com) => {
return (props) => {
const fieldSchema = useFieldSchema();
const { form } = useFormBlockContext();
const record = useRecord();
const scope = useVariableOptions({
collectionField: { uiSchema: fieldSchema },
form,
record,
uiSchema: fieldSchema,
noDisabled: true,
});
return <Com {...props} scope={scope} />;
};
};
export const useURLAndParamsSchema = () => {
const fieldSchema = useFieldSchema();
const { t } = useTranslation();
const Com = useMemo(() => getVariableComponentWithScope(Variable.TextArea), []);
const urlSchema = useMemo(() => {
return {
title: t('URL'),
type: 'string',
'x-decorator': 'FormItem',
'x-component': Com,
description: t('Do not concatenate search params in the URL'),
'x-reactions': {
dependencies: ['mode'],
fulfill: {
state: {
hidden: '{{$deps[0] === "html"}}',
},
},
},
};
}, [t, fieldSchema]);
const paramsSchema = useMemo(() => {
return {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: `{{t("Search parameters")}}`,
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
style: {
flexWrap: 'nowrap',
maxWidth: '100%',
},
className: css`
& > .ant-space-item:first-child,
& > .ant-space-item:last-child {
flex-shrink: 0;
}
`,
},
properties: {
name: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: `{{t("Name")}}`,
},
},
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': Com,
'x-component-props': {
placeholder: `{{t("Value")}}`,
useTypedConstant: true,
changeOnSelect: true,
},
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
'x-reactions': {
dependencies: ['mode'],
fulfill: {
state: {
hidden: '{{$deps[0] === "html"}}',
},
},
},
properties: {
add: {
type: 'void',
title: `{{t("Add parameter")}}`,
'x-component': 'ArrayItems.Addition',
},
},
};
}, [fieldSchema]);
return { urlSchema, paramsSchema };
};

View File

@ -11,10 +11,12 @@ import { FormLayout } from '@formily/antd-v5';
import { SchemaOptionsContext } from '@formily/react';
import React, { useCallback, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { FormDialog, SchemaComponent, SchemaComponentOptions } from '../../schema-component';
import { Router } from 'react-router-dom';
import { SchemaInitializerItem, useSchemaInitializer } from '../../application';
import { useGlobalTheme } from '../../global-theme';
import { FormDialog, SchemaComponent, SchemaComponentOptions } from '../../schema-component';
import { useStyles } from '../../schema-component/antd/menu/MenuItemInitializers';
import { useURLAndParamsSchema } from '../actions/link/useURLAndParamsSchema';
export const LinkMenuItem = () => {
const { insert } = useSchemaInitializer();
@ -22,45 +24,45 @@ export const LinkMenuItem = () => {
const options = useContext(SchemaOptionsContext);
const { theme } = useGlobalTheme();
const { styles } = useStyles();
const { urlSchema, paramsSchema } = useURLAndParamsSchema();
const handleClick = useCallback(async () => {
const values = await FormDialog(
t('Add link'),
() => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
properties: {
title: {
title: t('Menu item title'),
required: true,
'x-component': 'Input',
'x-decorator': 'FormItem',
<Router location={location} navigator={null}>
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
<FormLayout layout={'vertical'}>
<SchemaComponent
schema={{
properties: {
title: {
title: t('Menu item title'),
required: true,
'x-component': 'Input',
'x-decorator': 'FormItem',
},
icon: {
title: t('Icon'),
'x-component': 'IconPicker',
'x-decorator': 'FormItem',
},
href: urlSchema,
params: paramsSchema,
},
icon: {
title: t('Icon'),
'x-component': 'IconPicker',
'x-decorator': 'FormItem',
},
href: {
title: t('Link'),
'x-component': 'Input',
'x-decorator': 'FormItem',
},
},
}}
/>
</FormLayout>
</SchemaComponentOptions>
}}
/>
</FormLayout>
</SchemaComponentOptions>
</Router>
);
},
theme,
).open({
initialValues: {},
});
const { title, href, icon } = values;
const { title, href, params, icon } = values;
insert({
type: 'void',
title,
@ -69,6 +71,7 @@ export const LinkMenuItem = () => {
'x-component-props': {
icon,
href,
params,
},
'x-server-hooks': [
{

View File

@ -7,71 +7,107 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { uid } from '@formily/shared';
import { expect, groupPageEmpty, test } from '@nocobase/test/e2e';
test.describe('add menu item', () => {
test('header', async ({ page, deletePage }) => {
await page.goto('/');
const pageGroup = uid();
const pageItem = uid();
const pageLink = uid();
// add group
await page.getByTestId('schema-initializer-Menu-header').hover();
await page.getByRole('menuitem', { name: 'Group' }).click();
await page.getByRole('textbox').click();
await page.getByRole('textbox').fill('page group');
await page.getByRole('textbox').fill(pageGroup);
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByLabel('page group', { exact: true })).toBeVisible();
await expect(page.getByLabel(pageGroup, { exact: true })).toBeVisible();
// add page
await page.getByTestId('schema-initializer-Menu-header').hover();
await page.getByRole('menuitem', { name: 'Page', exact: true }).click();
await page.getByRole('textbox').click();
await page.getByRole('textbox').fill('page item');
await page.getByRole('textbox').fill(pageItem);
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByLabel('page item', { exact: true })).toBeVisible();
await expect(page.getByLabel(pageItem, { exact: true })).toBeVisible();
// add link
await page.getByTestId('schema-initializer-Menu-header').hover();
await page.getByRole('menuitem', { name: 'Link', exact: true }).click();
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').click();
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').fill('page link');
await page.getByLabel('block-item-Input-Link').getByRole('textbox').click();
await page.getByLabel('block-item-Input-Link').getByRole('textbox').fill('baidu.com');
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').fill(pageLink);
await page.getByLabel('block-item-URL').getByLabel('textbox').fill('baidu.com');
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByText('page link', { exact: true })).toBeVisible();
await expect(page.getByText(pageLink, { exact: true })).toBeVisible();
// delete pages
await deletePage('page group');
await deletePage('page item');
await deletePage('page link');
await deletePage(pageGroup);
await deletePage(pageItem);
await deletePage(pageLink);
});
test('sidebar', async ({ page, mockPage }) => {
await mockPage(groupPageEmpty).goto();
const pageGroupSide = uid();
const pageItemSide = uid();
const pageLinkSide = uid();
// add group in side
await page.getByTestId('schema-initializer-Menu-side').hover();
await page.getByRole('menuitem', { name: 'Group', exact: true }).click();
await page.getByRole('textbox').click();
await page.getByRole('textbox').fill('page group side');
await page.getByRole('textbox').fill(pageGroupSide);
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByText('page group side', { exact: true })).toBeVisible();
await expect(page.getByText(pageGroupSide, { exact: true })).toBeVisible();
// add page in side
await page.getByTestId('schema-initializer-Menu-side').hover();
await page.getByRole('menuitem', { name: 'Page', exact: true }).click();
await page.getByRole('textbox').click();
await page.getByRole('textbox').fill('page item side');
await page.getByRole('textbox').fill(pageItemSide);
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByText('page item side', { exact: true })).toBeVisible();
await expect(page.getByText(pageItemSide, { exact: true })).toBeVisible();
// add link in side
await page.getByTestId('schema-initializer-Menu-side').hover();
await page.getByRole('menuitem', { name: 'Link', exact: true }).click();
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').click();
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').fill('link item side');
await page.getByLabel('block-item-Input-Link').getByRole('textbox').click();
await page.getByLabel('block-item-Input-Link').getByRole('textbox').fill('/');
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').fill(pageLinkSide);
await page.getByLabel('block-item-URL').getByLabel('textbox').fill('/');
await page.getByRole('button', { name: 'OK', exact: true }).click();
await expect(page.getByText('link item side', { exact: true })).toBeVisible();
await expect(page.getByText(pageLinkSide, { exact: true })).toBeVisible();
});
test('link: use variable', async ({ page, deletePage }) => {
await page.goto('/');
const pageLink = uid();
const token = await page.evaluate(() => {
return window.localStorage.getItem('NOCOBASE_TOKEN');
});
// add link
await page.getByTestId('schema-initializer-Menu-header').hover();
await page.getByRole('menuitem', { name: 'Link', exact: true }).click();
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').click();
await page.getByLabel('block-item-Input-Menu item title').getByRole('textbox').fill(pageLink);
await page.getByLabel('block-item-URL').getByLabel('textbox').fill('https://www.baidu.com');
await page.getByRole('button', { name: 'plus Add parameter' }).click();
await page.getByPlaceholder('Name').click();
await page.getByPlaceholder('Name').fill('token');
await page.getByLabel('block-item-ArrayItems-Search').getByLabel('variable-button').click();
await page.getByRole('menuitemcheckbox', { name: 'API token' }).click();
await page.getByRole('button', { name: 'OK' }).click();
// open link page
await page.getByLabel(pageLink).click();
await page.waitForTimeout(1000);
// After clicking, it will redirect to another page, so we need to get the instance of the new page
const newPage = page.context().pages()[1];
expect(newPage.url()).toBe('https://www.baidu.com/?token=' + token);
await deletePage(pageLink);
});
});

View File

@ -22,6 +22,7 @@ import {
SchemaSettingsSubMenu,
useAPIClient,
useDesignable,
useURLAndParamsSchema,
} from '../../../';
const toItems = (properties = {}) => {
@ -60,6 +61,7 @@ const InsertMenuItems = (props) => {
const { t } = useTranslation();
const { dn } = useDesignable();
const fieldSchema = useFieldSchema();
const { urlSchema, paramsSchema } = useURLAndParamsSchema();
const isSubMenu = fieldSchema['x-component'] === 'Menu.SubMenu';
if (!isSubMenu && insertPosition === 'beforeEnd') {
return null;
@ -184,15 +186,12 @@ const InsertMenuItems = (props) => {
'x-component': 'IconPicker',
'x-decorator': 'FormItem',
},
href: {
title: t('Link'),
'x-component': 'Input',
'x-decorator': 'FormItem',
},
href: urlSchema,
params: paramsSchema,
},
} as ISchema
}
onSubmit={({ title, icon, href }) => {
onSubmit={({ title, icon, href, params }) => {
dn.insertAdjacent(insertPosition, {
type: 'void',
title,
@ -201,6 +200,7 @@ const InsertMenuItems = (props) => {
'x-component-props': {
icon,
href,
params,
},
'x-server-hooks': serverHooks,
});
@ -220,6 +220,7 @@ export const MenuDesigner = () => {
const { t } = useTranslation();
const menuSchema = findMenuSchema(fieldSchema);
const compile = useCompile();
const { urlSchema, paramsSchema } = useURLAndParamsSchema();
const onSelect = useMemo(
() => compile(menuSchema?.['x-component-props']?.['onSelect']),
[menuSchema?.['x-component-props']?.['onSelect']],
@ -273,15 +274,13 @@ export const MenuDesigner = () => {
};
}, [field]);
if (fieldSchema['x-component'] === 'Menu.URL') {
schema.properties['href'] = {
title: t('Link'),
'x-component': 'Input',
'x-decorator': 'FormItem',
};
schema.properties['href'] = urlSchema;
schema.properties['params'] = paramsSchema;
initialValues['href'] = field.componentProps.href;
initialValues['params'] = field.componentProps.params;
}
const onEditSubmit: (values: any) => void = useCallback(
({ title, icon, href }) => {
({ title, icon, href, params }) => {
const schema = {
['x-uid']: fieldSchema['x-uid'],
'x-server-hooks': [
@ -299,10 +298,12 @@ export const MenuDesigner = () => {
}
field.componentProps.icon = icon;
field.componentProps.href = href;
schema['x-component-props'] = { icon, href };
field.componentProps.params = params;
schema['x-component-props'] = { icon, href, params };
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
fieldSchema['x-component-props']['icon'] = icon;
fieldSchema['x-component-props']['href'] = href;
fieldSchema['x-component-props']['params'] = params;
onSelect?.({ item: { props: { schema: fieldSchema } } });
dn.emit('patch', {
schema,

View File

@ -24,7 +24,7 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useR
import { createPortal } from 'react-dom';
import { useTranslation } from 'react-i18next';
import { createDesignable, DndContext, SortableItem, useDesignable, useDesigner } from '../..';
import { Icon, useAPIClient, useSchemaInitializerRender } from '../../../';
import { Icon, useAPIClient, useParseURLAndParams, useSchemaInitializerRender } from '../../../';
import { useCollectMenuItems, useMenuItem } from '../../../hooks/useMenuItem';
import { useProps } from '../../hooks/useProps';
import { useMenuTranslation } from './locale';
@ -550,9 +550,55 @@ Menu.Item = observer(
{ displayName: 'Menu.Item' },
);
const MenuURLButton = ({ href, params, icon }) => {
const field = useField();
const { t } = useMenuTranslation();
const Designer = useContext(MenuItemDesignerContext);
const { parseURLAndParams } = useParseURLAndParams();
const urlRef = useRef(href);
useEffect(() => {
const run = async () => {
try {
urlRef.current = await parseURLAndParams(href, params || []);
} catch (err) {
console.error(err);
urlRef.current = href;
}
};
run();
}, [href, JSON.stringify(params), parseURLAndParams]);
return (
<SortableItem
className={designerCss}
onClick={(event) => {
window.open(urlRef.current, '_blank');
event.preventDefault();
event.stopPropagation();
}}
removeParentsIfNoChildren={false}
aria-label={t(field.title)}
>
<Icon type={icon} />
<span
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'inline-block',
width: '100%',
verticalAlign: 'middle',
}}
>
{t(field.title)}
</span>
<Designer />
</SortableItem>
);
};
Menu.URL = observer(
(props) => {
const { t } = useMenuTranslation();
const { pushMenuItem } = useCollectMenuItems();
const { icon, children, ...others } = props;
const schema = useFieldSchema();
@ -575,35 +621,12 @@ Menu.URL = observer(
label: (
<SchemaContext.Provider value={schema}>
<FieldContext.Provider value={field}>
<SortableItem
className={designerCss}
onClick={(event) => {
window.open(props.href, '_blank');
event.preventDefault();
event.stopPropagation();
}}
removeParentsIfNoChildren={false}
aria-label={t(field.title)}
>
<Icon type={icon} />
<span
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'inline-block',
width: '100%',
verticalAlign: 'middle',
}}
>
{t(field.title)}
</span>
<Designer />
</SortableItem>
<MenuURLButton icon={icon} href={props.href} params={props.params} />
</FieldContext.Provider>
</SchemaContext.Provider>
),
};
}, [field.title, icon, props.href, schema, Designer]);
}, [field.title, icon, props.href, schema, JSON.stringify(props.params)]);
pushMenuItem(item);
return null;

View File

@ -9,11 +9,10 @@
import { observer, useField } from '@formily/react';
import {
appendQueryStringToUrl,
parseVariablesAndChangeParamsToQueryString,
replaceVariableValue,
useBlockHeight,
useLocalVariables,
useParseURLAndParams,
useRequest,
useVariables,
} from '@nocobase/client';
@ -49,7 +48,7 @@ export const Iframe: any = observer(
ready: mode === 'html' && !!htmlId,
},
);
const { parseURLAndParams } = useParseURLAndParams();
const [src, setSrc] = useState(null);
useEffect(() => {
@ -61,15 +60,7 @@ export const Iframe: any = observer(
setSrc(dataUrl);
} else {
try {
const tempUrl = await replaceVariableValue(url, variables, localVariables);
const queryString = await parseVariablesAndChangeParamsToQueryString({
searchParams: params || [],
variables,
localVariables,
replaceVariableValue,
});
const targetUrl = appendQueryStringToUrl(tempUrl, queryString);
const targetUrl = parseURLAndParams(url, params || []);
setSrc(targetUrl);
} catch (error) {
console.error('Error fetching target URL:', error);

View File

@ -7,17 +7,17 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { css } from '@emotion/css';
import { ISchema, useField, useFieldSchema } from '@formily/react';
import { uid } from '@formily/shared';
import {
SchemaSettings,
SchemaSettingsBlockHeightItem,
Variable,
useAPIClient,
useDesignable,
SchemaSettingsBlockHeightItem,
useFormBlockContext,
useRecord,
useURLAndParamsSchema,
useVariableOptions,
} from '@nocobase/client';
import React from 'react';
@ -50,7 +50,7 @@ const commonOptions: any = {
const { t } = useTranslation();
const { dn } = useDesignable();
const api = useAPIClient();
const { mode, url, htmlId, height = '60vh' } = fieldSchema['x-component-props'] || {};
const { mode, url, params, htmlId, height = '60vh' } = fieldSchema['x-component-props'] || {};
const saveHtml = async (html: string) => {
const options = {
values: { html },
@ -65,7 +65,7 @@ const commonOptions: any = {
return data?.data;
}
};
const { urlSchema, paramsSchema } = useURLAndParamsSchema();
const submitHandler = async ({ mode, url, html, height, params }) => {
const componentProps = fieldSchema['x-component-props'] || {};
componentProps['mode'] = mode;
@ -94,6 +94,7 @@ const commonOptions: any = {
mode,
url,
height,
params,
};
if (htmlId) {
// eslint-disable-next-line no-unsafe-optional-chaining
@ -118,89 +119,10 @@ const commonOptions: any = {
],
},
url: {
title: t('URL'),
type: 'string',
'x-decorator': 'FormItem',
'x-component': getVariableComponentWithScope(Variable.TextArea),
description: t('Do not concatenate search params in the URL'),
...urlSchema,
required: true,
'x-reactions': {
dependencies: ['mode'],
fulfill: {
state: {
hidden: '{{$deps[0] === "html"}}',
},
},
},
},
params: {
type: 'array',
'x-component': 'ArrayItems',
'x-decorator': 'FormItem',
title: `{{t("Search parameters")}}`,
default: fieldSchema?.['x-component-props']?.params || [{}],
items: {
type: 'object',
properties: {
space: {
type: 'void',
'x-component': 'Space',
'x-component-props': {
style: {
flexWrap: 'nowrap',
maxWidth: '100%',
},
className: css`
& > .ant-space-item:first-child,
& > .ant-space-item:last-child {
flex-shrink: 0;
}
`,
},
properties: {
name: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-component-props': {
placeholder: `{{t("Name")}}`,
},
},
value: {
type: 'string',
'x-decorator': 'FormItem',
'x-component': getVariableComponentWithScope(Variable.TextArea),
'x-component-props': {
placeholder: `{{t("Value")}}`,
useTypedConstant: true,
changeOnSelect: true,
},
},
remove: {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'ArrayItems.Remove',
},
},
},
},
},
'x-reactions': {
dependencies: ['mode'],
fulfill: {
state: {
hidden: '{{$deps[0] === "html"}}',
},
},
},
properties: {
add: {
type: 'void',
title: `{{t("Add parameter")}}`,
'x-component': 'ArrayItems.Addition',
},
},
},
params: paramsSchema,
html: {
title: t('html'),
type: 'string',