mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 15:09:27 +08:00
feat: add support for helper functions in variable configuration and update related components
This commit is contained in:
parent
9d5bb0a069
commit
4d88c083bb
@ -14,12 +14,14 @@ import { Dropdown, Tag } from 'antd';
|
||||
import React from 'react';
|
||||
import { useApp } from '../../../../application';
|
||||
import { useCompile } from '../../../hooks';
|
||||
import { isHelperAllowedForVariable, useVariable } from '../VariableProvider';
|
||||
import { useHelperObservables } from './hooks/useHelperObservables';
|
||||
import { allHelpersConfigObs } from './observables';
|
||||
|
||||
export const HelperAddition = observer(() => {
|
||||
const app = useApp();
|
||||
const helperObservables = useHelperObservables();
|
||||
const { isHelperAllowed } = useVariable();
|
||||
const { addHelper } = helperObservables;
|
||||
const compile = useCompile();
|
||||
const filterOptions = app.jsonTemplateParser.filterGroups
|
||||
@ -28,31 +30,35 @@ export const HelperAddition = observer(() => {
|
||||
key: group.name,
|
||||
type: 'group',
|
||||
label: compile(group.title),
|
||||
children: group.filters
|
||||
children: group.helpers
|
||||
.filter(({ name }) => isHelperAllowed([group.name, name].join('.')))
|
||||
.sort((a, b) => a.sort - b.sort)
|
||||
.map((filter) => ({ key: filter.name, label: compile(filter.title) })),
|
||||
})) as MenuProps['items'];
|
||||
}))
|
||||
.filter((group) => group.children.length > 0) as MenuProps['items'];
|
||||
|
||||
const items = allHelpersConfigObs.value.map((helper) => ({
|
||||
key: helper.name,
|
||||
label: helper.title,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<span style={{ color: '#bfbfbf', margin: '0 5px' }}>|</span>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: filterOptions,
|
||||
onClick: ({ key }) => {
|
||||
addHelper({ name: key });
|
||||
},
|
||||
}}
|
||||
>
|
||||
<a onClick={(e) => e.preventDefault()}>
|
||||
<FilterOutlined style={{ color: '#52c41a' }} />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
if (filterOptions.length > 0) {
|
||||
return (
|
||||
<>
|
||||
<span style={{ color: '#bfbfbf', margin: '0 5px' }}>|</span>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: filterOptions,
|
||||
onClick: ({ key }) => {
|
||||
addHelper({ name: key });
|
||||
},
|
||||
}}
|
||||
>
|
||||
<a onClick={(e) => e.preventDefault()}>
|
||||
<FilterOutlined style={{ color: '#52c41a' }} />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
@ -51,7 +51,7 @@ export function isFilterAllowedForVariable(
|
||||
// But we escape the variable name since it's a literal value
|
||||
if (minimatch(escapeGlob(variableName), rule.variable)) {
|
||||
// Check if filter matches any of the allowed patterns
|
||||
return rule.filters.some((pattern) => minimatch(filterName, pattern));
|
||||
return rule.helpers.some((pattern) => minimatch(filterName, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { Helper } from '@nocobase/json-template-parser';
|
||||
import { isArray } from 'lodash';
|
||||
import minimatch from 'minimatch';
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
@ -15,6 +16,8 @@ import { useHelperObservables } from './Helpers/hooks/useHelperObservables';
|
||||
interface VariableContextValue {
|
||||
value: any;
|
||||
helperObservables?: ReturnType<typeof useHelperObservables>;
|
||||
variableHelperMapping: VariableHelperMapping;
|
||||
variableName: string;
|
||||
}
|
||||
|
||||
interface VariableProviderProps {
|
||||
@ -28,7 +31,7 @@ export interface VariableHelperRule {
|
||||
/** Pattern to match variables, supports glob patterns */
|
||||
variable: string;
|
||||
/** Array of allowed filter patterns, supports glob patterns */
|
||||
filters: string[];
|
||||
helpers: string[];
|
||||
}
|
||||
|
||||
export interface VariableHelperMapping {
|
||||
@ -50,13 +53,13 @@ function escapeGlob(str: string): string {
|
||||
/**
|
||||
* Tests if a filter is allowed for a given variable based on the variableHelperMapping configuration
|
||||
* @param variableName The name of the variable to test
|
||||
* @param filterName The name of the filter to test
|
||||
* @param helperName The name of the filter to test
|
||||
* @param mapping The variable helper mapping configuration
|
||||
* @returns boolean indicating if the filter is allowed for the variable
|
||||
*/
|
||||
export function isFilterAllowedForVariable(
|
||||
export function isHelperAllowedForVariable(
|
||||
variableName: string,
|
||||
filterName: string,
|
||||
helperName: string,
|
||||
mapping?: VariableHelperMapping,
|
||||
): boolean {
|
||||
if (!mapping?.rules) {
|
||||
@ -68,30 +71,31 @@ export function isFilterAllowedForVariable(
|
||||
// Check if variable matches the pattern
|
||||
// We don't escape the pattern since it's meant to be a glob pattern
|
||||
// But we escape the variable name since it's a literal value
|
||||
if (minimatch(escapeGlob(variableName), rule.variable)) {
|
||||
const matched = minimatch(variableName, rule.variable);
|
||||
if (matched) {
|
||||
// Check if filter matches any of the allowed patterns
|
||||
return rule.filters.some((pattern) => minimatch(filterName, pattern));
|
||||
return rule.helpers.some((pattern) => minimatch(helperName, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
// If no matching rule found and strictMode is true, deny the filter
|
||||
return !mapping.strictMode;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all supported filters for a given variable based on the mapping rules
|
||||
* @param variableName The name of the variable to check
|
||||
* @param mapping The variable helper mapping configuration
|
||||
* @param allFilters Array of all available filter names
|
||||
* @param allHelpers Array of all available filter names
|
||||
* @returns Array of filter names that are allowed for the variable
|
||||
*/
|
||||
export function getSupportedFiltersForVariable(
|
||||
variableName: string,
|
||||
mapping?: VariableHelperMapping,
|
||||
allFilters: string[] = [],
|
||||
): string[] {
|
||||
allHelpers: Helper[] = [],
|
||||
): Helper[] {
|
||||
if (!mapping?.rules) {
|
||||
return allFilters; // If no rules defined, all filters are allowed
|
||||
return allHelpers; // If no rules defined, all filters are allowed
|
||||
}
|
||||
|
||||
// Find matching rule for the variable
|
||||
@ -100,14 +104,18 @@ export function getSupportedFiltersForVariable(
|
||||
if (!matchingRule) {
|
||||
// If no matching rule and strictMode is true, return empty array
|
||||
// Otherwise return all filters
|
||||
return mapping.strictMode ? [] : allFilters;
|
||||
return allHelpers;
|
||||
}
|
||||
|
||||
// Filter the allFilters array based on the matching rule's filter patterns
|
||||
return allFilters.filter((filterName) => matchingRule.filters.some((pattern) => minimatch(filterName, pattern)));
|
||||
return allHelpers.filter(({ name }) => matchingRule.helpers.some((pattern) => minimatch(name, pattern)));
|
||||
}
|
||||
|
||||
const VariableContext = createContext<VariableContextValue>({ value: null });
|
||||
const VariableContext = createContext<VariableContextValue>({
|
||||
variableName: '',
|
||||
value: null,
|
||||
variableHelperMapping: { rules: [] },
|
||||
});
|
||||
|
||||
export function useCurrentVariable(): VariableContextValue {
|
||||
const context = useContext(VariableContext);
|
||||
@ -117,7 +125,12 @@ export function useCurrentVariable(): VariableContextValue {
|
||||
return context;
|
||||
}
|
||||
|
||||
export const VariableProvider: React.FC<VariableProviderProps> = ({ variableName, children, helperObservables }) => {
|
||||
export const VariableProvider: React.FC<VariableProviderProps> = ({
|
||||
variableName,
|
||||
children,
|
||||
helperObservables,
|
||||
variableHelperMapping,
|
||||
}) => {
|
||||
const [value, setValue] = useState(null);
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables();
|
||||
@ -134,5 +147,28 @@ export const VariableProvider: React.FC<VariableProviderProps> = ({ variableName
|
||||
fetchValue();
|
||||
}, [localVariables, variableName, variables]);
|
||||
|
||||
return <VariableContext.Provider value={{ value, helperObservables }}>{children}</VariableContext.Provider>;
|
||||
return (
|
||||
<VariableContext.Provider value={{ variableName, value, helperObservables, variableHelperMapping }}>
|
||||
{children}
|
||||
</VariableContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useVariable() {
|
||||
const context = useContext(VariableContext);
|
||||
const { value, variableName, variableHelperMapping } = context;
|
||||
|
||||
const isHelperAllowed = (filterName: string) => {
|
||||
return isHelperAllowedForVariable(variableName, filterName, variableHelperMapping);
|
||||
};
|
||||
|
||||
const getSupportedFilters = (allHelpers: Helper[]) => {
|
||||
return getSupportedFiltersForVariable(variableName, variableHelperMapping, allHelpers);
|
||||
};
|
||||
|
||||
return {
|
||||
...context,
|
||||
isHelperAllowed,
|
||||
getSupportedFilters,
|
||||
};
|
||||
}
|
||||
|
@ -13,11 +13,19 @@ const schema = {
|
||||
properties: {
|
||||
input: {
|
||||
type: 'string',
|
||||
title: `替换模式`,
|
||||
title: `输入项`,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Variable.Input',
|
||||
'x-component-props': {
|
||||
scope,
|
||||
variableHelperMapping: {
|
||||
rules: [
|
||||
{
|
||||
variable: '$date.*',
|
||||
helpers: ['date.*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -28,10 +28,27 @@ type ParseOptions = {
|
||||
|
||||
<code src="./demos/demo1.tsx"></code>
|
||||
|
||||
### `Variable.TextArea`
|
||||
### 支持 helper 助手函数
|
||||
目前变量支持添加助手函数进行二次处理,不同的变量可能支持不同的助手函数,助手函数还支持分组。
|
||||
Input 组件支持传入 variableHelperMapping 属性来标记变量支持哪些助手函数。
|
||||
例如,日期变量只支持日期相关的助手函数,则可配置
|
||||
```ts
|
||||
const variableHelperMapping = {
|
||||
rules: [
|
||||
{
|
||||
variable: '$date.*',
|
||||
helpers: ['date.*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
```
|
||||
<code src="./demos/demo1.tsx"></code>
|
||||
|
||||
## `Variable.TextArea`
|
||||
|
||||
<code src="./demos/demo2.tsx"></code>
|
||||
|
||||
### `Variable.JSON`
|
||||
## `Variable.JSON`
|
||||
|
||||
<code src="./demos/demo3.tsx"></code>
|
||||
|
@ -16,7 +16,7 @@ type FilterGroup = {
|
||||
sort: number;
|
||||
};
|
||||
|
||||
type Filter = {
|
||||
type Helper = {
|
||||
name: string;
|
||||
title: string;
|
||||
handler: (...args: any[]) => any;
|
||||
@ -40,7 +40,7 @@ type ScopeMapValue = { fieldSet: Set<string>; scopeFnWrapper: ScopeFnWrapper; sc
|
||||
export class JSONTemplateParser {
|
||||
private _engine: Liquid;
|
||||
private _filterGroups: Array<FilterGroup>;
|
||||
private _filters: Array<Filter>;
|
||||
private _filters: Array<Helper>;
|
||||
|
||||
constructor() {
|
||||
this._engine = new Liquid();
|
||||
@ -48,7 +48,7 @@ export class JSONTemplateParser {
|
||||
this._filters = [];
|
||||
}
|
||||
|
||||
get filters(): Array<Filter> {
|
||||
get filters(): Array<Helper> {
|
||||
return this._filters;
|
||||
}
|
||||
|
||||
@ -58,19 +58,19 @@ export class JSONTemplateParser {
|
||||
|
||||
get filterGroups(): Array<
|
||||
FilterGroup & {
|
||||
filters: Array<Filter>;
|
||||
helpers: Array<Helper>;
|
||||
}
|
||||
> {
|
||||
return this._filterGroups.map((group) => ({
|
||||
...group,
|
||||
filters: this._filters.filter((filter) => filter.group === group.name),
|
||||
helpers: this._filters.filter((filter) => filter.group === group.name),
|
||||
}));
|
||||
}
|
||||
|
||||
registerFilterGroup(group: FilterGroup): void {
|
||||
this._filterGroups.push(group);
|
||||
}
|
||||
registerFilter(filter: Filter): void {
|
||||
registerFilter(filter: Helper): void {
|
||||
this._filters.push(filter);
|
||||
this._engine.registerFilter(filter.name, filter.handler);
|
||||
}
|
||||
|
@ -7,12 +7,12 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { escapeSpecialChars, escape, revertEscape } from '../escape';
|
||||
import { escape, escapeSpecialChars, revertEscape } from '../escape';
|
||||
import { createJSONTemplateParser } from '../parser';
|
||||
|
||||
const parser = createJSONTemplateParser();
|
||||
const engine = parser.engine;
|
||||
type Filter = {
|
||||
export type Helper = {
|
||||
name: string;
|
||||
handler: any;
|
||||
args: string[];
|
||||
@ -31,7 +31,7 @@ export function extractTemplateVariable(template: string): string | null {
|
||||
export function extractTemplateElements(template: string): {
|
||||
fullVariable: string | null;
|
||||
variableSegments: string[];
|
||||
helpers: Filter[];
|
||||
helpers: Helper[];
|
||||
} {
|
||||
const escapedTemplate = escape(template ?? '');
|
||||
try {
|
||||
@ -52,7 +52,7 @@ export function extractTemplateElements(template: string): {
|
||||
}
|
||||
}
|
||||
|
||||
const composeFilterTemplate = (filter: Filter) => {
|
||||
const composeFilterTemplate = (filter: Helper) => {
|
||||
const value = `${filter.name}${
|
||||
filter.args.length ? `:${filter.args.map((val) => JSON.stringify(val)).join(',')}` : ''
|
||||
}`;
|
||||
|
Loading…
x
Reference in New Issue
Block a user