mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-08 23:19:26 +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 React from 'react';
|
||||||
import { useApp } from '../../../../application';
|
import { useApp } from '../../../../application';
|
||||||
import { useCompile } from '../../../hooks';
|
import { useCompile } from '../../../hooks';
|
||||||
|
import { isHelperAllowedForVariable, useVariable } from '../VariableProvider';
|
||||||
import { useHelperObservables } from './hooks/useHelperObservables';
|
import { useHelperObservables } from './hooks/useHelperObservables';
|
||||||
import { allHelpersConfigObs } from './observables';
|
import { allHelpersConfigObs } from './observables';
|
||||||
|
|
||||||
export const HelperAddition = observer(() => {
|
export const HelperAddition = observer(() => {
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
const helperObservables = useHelperObservables();
|
const helperObservables = useHelperObservables();
|
||||||
|
const { isHelperAllowed } = useVariable();
|
||||||
const { addHelper } = helperObservables;
|
const { addHelper } = helperObservables;
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const filterOptions = app.jsonTemplateParser.filterGroups
|
const filterOptions = app.jsonTemplateParser.filterGroups
|
||||||
@ -28,31 +30,35 @@ export const HelperAddition = observer(() => {
|
|||||||
key: group.name,
|
key: group.name,
|
||||||
type: 'group',
|
type: 'group',
|
||||||
label: compile(group.title),
|
label: compile(group.title),
|
||||||
children: group.filters
|
children: group.helpers
|
||||||
|
.filter(({ name }) => isHelperAllowed([group.name, name].join('.')))
|
||||||
.sort((a, b) => a.sort - b.sort)
|
.sort((a, b) => a.sort - b.sort)
|
||||||
.map((filter) => ({ key: filter.name, label: compile(filter.title) })),
|
.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) => ({
|
const items = allHelpersConfigObs.value.map((helper) => ({
|
||||||
key: helper.name,
|
key: helper.name,
|
||||||
label: helper.title,
|
label: helper.title,
|
||||||
}));
|
}));
|
||||||
|
if (filterOptions.length > 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span style={{ color: '#bfbfbf', margin: '0 5px' }}>|</span>
|
<span style={{ color: '#bfbfbf', margin: '0 5px' }}>|</span>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: filterOptions,
|
items: filterOptions,
|
||||||
onClick: ({ key }) => {
|
onClick: ({ key }) => {
|
||||||
addHelper({ name: key });
|
addHelper({ name: key });
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a onClick={(e) => e.preventDefault()}>
|
<a onClick={(e) => e.preventDefault()}>
|
||||||
<FilterOutlined style={{ color: '#52c41a' }} />
|
<FilterOutlined style={{ color: '#52c41a' }} />
|
||||||
</a>
|
</a>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
@ -51,7 +51,7 @@ export function isFilterAllowedForVariable(
|
|||||||
// But we escape the variable name since it's a literal value
|
// But we escape the variable name since it's a literal value
|
||||||
if (minimatch(escapeGlob(variableName), rule.variable)) {
|
if (minimatch(escapeGlob(variableName), rule.variable)) {
|
||||||
// Check if filter matches any of the allowed patterns
|
// 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.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Helper } from '@nocobase/json-template-parser';
|
||||||
import { isArray } from 'lodash';
|
import { isArray } from 'lodash';
|
||||||
import minimatch from 'minimatch';
|
import minimatch from 'minimatch';
|
||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
@ -15,6 +16,8 @@ import { useHelperObservables } from './Helpers/hooks/useHelperObservables';
|
|||||||
interface VariableContextValue {
|
interface VariableContextValue {
|
||||||
value: any;
|
value: any;
|
||||||
helperObservables?: ReturnType<typeof useHelperObservables>;
|
helperObservables?: ReturnType<typeof useHelperObservables>;
|
||||||
|
variableHelperMapping: VariableHelperMapping;
|
||||||
|
variableName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VariableProviderProps {
|
interface VariableProviderProps {
|
||||||
@ -28,7 +31,7 @@ export interface VariableHelperRule {
|
|||||||
/** Pattern to match variables, supports glob patterns */
|
/** Pattern to match variables, supports glob patterns */
|
||||||
variable: string;
|
variable: string;
|
||||||
/** Array of allowed filter patterns, supports glob patterns */
|
/** Array of allowed filter patterns, supports glob patterns */
|
||||||
filters: string[];
|
helpers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VariableHelperMapping {
|
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
|
* 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 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
|
* @param mapping The variable helper mapping configuration
|
||||||
* @returns boolean indicating if the filter is allowed for the variable
|
* @returns boolean indicating if the filter is allowed for the variable
|
||||||
*/
|
*/
|
||||||
export function isFilterAllowedForVariable(
|
export function isHelperAllowedForVariable(
|
||||||
variableName: string,
|
variableName: string,
|
||||||
filterName: string,
|
helperName: string,
|
||||||
mapping?: VariableHelperMapping,
|
mapping?: VariableHelperMapping,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!mapping?.rules) {
|
if (!mapping?.rules) {
|
||||||
@ -68,30 +71,31 @@ export function isFilterAllowedForVariable(
|
|||||||
// Check if variable matches the pattern
|
// Check if variable matches the pattern
|
||||||
// We don't escape the pattern since it's meant to be a glob 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
|
// 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
|
// 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
|
// 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
|
* Gets all supported filters for a given variable based on the mapping rules
|
||||||
* @param variableName The name of the variable to check
|
* @param variableName The name of the variable to check
|
||||||
* @param mapping The variable helper mapping configuration
|
* @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
|
* @returns Array of filter names that are allowed for the variable
|
||||||
*/
|
*/
|
||||||
export function getSupportedFiltersForVariable(
|
export function getSupportedFiltersForVariable(
|
||||||
variableName: string,
|
variableName: string,
|
||||||
mapping?: VariableHelperMapping,
|
mapping?: VariableHelperMapping,
|
||||||
allFilters: string[] = [],
|
allHelpers: Helper[] = [],
|
||||||
): string[] {
|
): Helper[] {
|
||||||
if (!mapping?.rules) {
|
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
|
// Find matching rule for the variable
|
||||||
@ -100,14 +104,18 @@ export function getSupportedFiltersForVariable(
|
|||||||
if (!matchingRule) {
|
if (!matchingRule) {
|
||||||
// If no matching rule and strictMode is true, return empty array
|
// If no matching rule and strictMode is true, return empty array
|
||||||
// Otherwise return all filters
|
// Otherwise return all filters
|
||||||
return mapping.strictMode ? [] : allFilters;
|
return allHelpers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter the allFilters array based on the matching rule's filter patterns
|
// 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 {
|
export function useCurrentVariable(): VariableContextValue {
|
||||||
const context = useContext(VariableContext);
|
const context = useContext(VariableContext);
|
||||||
@ -117,7 +125,12 @@ export function useCurrentVariable(): VariableContextValue {
|
|||||||
return context;
|
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 [value, setValue] = useState(null);
|
||||||
const variables = useVariables();
|
const variables = useVariables();
|
||||||
const localVariables = useLocalVariables();
|
const localVariables = useLocalVariables();
|
||||||
@ -134,5 +147,28 @@ export const VariableProvider: React.FC<VariableProviderProps> = ({ variableName
|
|||||||
fetchValue();
|
fetchValue();
|
||||||
}, [localVariables, variableName, variables]);
|
}, [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: {
|
properties: {
|
||||||
input: {
|
input: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: `替换模式`,
|
title: `输入项`,
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Variable.Input',
|
'x-component': 'Variable.Input',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
scope,
|
scope,
|
||||||
|
variableHelperMapping: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
variable: '$date.*',
|
||||||
|
helpers: ['date.*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -28,10 +28,27 @@ type ParseOptions = {
|
|||||||
|
|
||||||
<code src="./demos/demo1.tsx"></code>
|
<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>
|
<code src="./demos/demo2.tsx"></code>
|
||||||
|
|
||||||
### `Variable.JSON`
|
## `Variable.JSON`
|
||||||
|
|
||||||
<code src="./demos/demo3.tsx"></code>
|
<code src="./demos/demo3.tsx"></code>
|
||||||
|
@ -16,7 +16,7 @@ type FilterGroup = {
|
|||||||
sort: number;
|
sort: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Filter = {
|
type Helper = {
|
||||||
name: string;
|
name: string;
|
||||||
title: string;
|
title: string;
|
||||||
handler: (...args: any[]) => any;
|
handler: (...args: any[]) => any;
|
||||||
@ -40,7 +40,7 @@ type ScopeMapValue = { fieldSet: Set<string>; scopeFnWrapper: ScopeFnWrapper; sc
|
|||||||
export class JSONTemplateParser {
|
export class JSONTemplateParser {
|
||||||
private _engine: Liquid;
|
private _engine: Liquid;
|
||||||
private _filterGroups: Array<FilterGroup>;
|
private _filterGroups: Array<FilterGroup>;
|
||||||
private _filters: Array<Filter>;
|
private _filters: Array<Helper>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._engine = new Liquid();
|
this._engine = new Liquid();
|
||||||
@ -48,7 +48,7 @@ export class JSONTemplateParser {
|
|||||||
this._filters = [];
|
this._filters = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
get filters(): Array<Filter> {
|
get filters(): Array<Helper> {
|
||||||
return this._filters;
|
return this._filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,19 +58,19 @@ export class JSONTemplateParser {
|
|||||||
|
|
||||||
get filterGroups(): Array<
|
get filterGroups(): Array<
|
||||||
FilterGroup & {
|
FilterGroup & {
|
||||||
filters: Array<Filter>;
|
helpers: Array<Helper>;
|
||||||
}
|
}
|
||||||
> {
|
> {
|
||||||
return this._filterGroups.map((group) => ({
|
return this._filterGroups.map((group) => ({
|
||||||
...group,
|
...group,
|
||||||
filters: this._filters.filter((filter) => filter.group === group.name),
|
helpers: this._filters.filter((filter) => filter.group === group.name),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
registerFilterGroup(group: FilterGroup): void {
|
registerFilterGroup(group: FilterGroup): void {
|
||||||
this._filterGroups.push(group);
|
this._filterGroups.push(group);
|
||||||
}
|
}
|
||||||
registerFilter(filter: Filter): void {
|
registerFilter(filter: Helper): void {
|
||||||
this._filters.push(filter);
|
this._filters.push(filter);
|
||||||
this._engine.registerFilter(filter.name, filter.handler);
|
this._engine.registerFilter(filter.name, filter.handler);
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* 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';
|
import { createJSONTemplateParser } from '../parser';
|
||||||
|
|
||||||
const parser = createJSONTemplateParser();
|
const parser = createJSONTemplateParser();
|
||||||
const engine = parser.engine;
|
const engine = parser.engine;
|
||||||
type Filter = {
|
export type Helper = {
|
||||||
name: string;
|
name: string;
|
||||||
handler: any;
|
handler: any;
|
||||||
args: string[];
|
args: string[];
|
||||||
@ -31,7 +31,7 @@ export function extractTemplateVariable(template: string): string | null {
|
|||||||
export function extractTemplateElements(template: string): {
|
export function extractTemplateElements(template: string): {
|
||||||
fullVariable: string | null;
|
fullVariable: string | null;
|
||||||
variableSegments: string[];
|
variableSegments: string[];
|
||||||
helpers: Filter[];
|
helpers: Helper[];
|
||||||
} {
|
} {
|
||||||
const escapedTemplate = escape(template ?? '');
|
const escapedTemplate = escape(template ?? '');
|
||||||
try {
|
try {
|
||||||
@ -52,7 +52,7 @@ export function extractTemplateElements(template: string): {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const composeFilterTemplate = (filter: Filter) => {
|
const composeFilterTemplate = (filter: Helper) => {
|
||||||
const value = `${filter.name}${
|
const value = `${filter.name}${
|
||||||
filter.args.length ? `:${filter.args.map((val) => JSON.stringify(val)).join(',')}` : ''
|
filter.args.length ? `:${filter.args.map((val) => JSON.stringify(val)).join(',')}` : ''
|
||||||
}`;
|
}`;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user