feat: support extending frontend filter operators (#6085)

* feat: operator extension

* fix: bug

* refactor: code improve

* fix: jsonLogic

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
Katherine 2025-02-15 11:14:36 +08:00 committed by GitHub
parent 3df4c0944d
commit 583ef1b98a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 269 additions and 223 deletions

View File

@ -2,9 +2,7 @@
"version": "1.6.0-alpha.25",
"npmClient": "yarn",
"useWorkspaces": true,
"npmClientArgs": [
"--ignore-engines"
],
"npmClientArgs": ["--ignore-engines"],
"command": {
"version": {
"forcePublish": true,

View File

@ -44,8 +44,14 @@ import type { CollectionFieldInterfaceFactory } from '../data-source';
import { OpenModeProvider } from '../modules/popup/OpenModeProvider';
import { AppSchemaComponentProvider } from './AppSchemaComponentProvider';
import type { Plugin } from './Plugin';
import { getOperators } from './globalOperators';
import type { RequireJS } from './utils/requirejs';
type JsonLogic = {
addOperation: (name: string, fn?: any) => void;
rmOperation: (name: string) => void;
};
declare global {
interface Window {
define: RequireJS['define'];
@ -100,7 +106,7 @@ export class Application {
public dataSourceManager: DataSourceManager;
public name: string;
public globalVars: Record<string, any> = {};
public jsonLogic: JsonLogic;
loading = true;
maintained = false;
maintaining = false;
@ -155,6 +161,7 @@ export class Application {
this.apiClient.auth.locale = lng;
});
this.initListeners();
this.jsonLogic = getOperators();
}
private initListeners() {
@ -488,4 +495,11 @@ export class Application {
getGlobalVar(key) {
return get(this.globalVars, key);
}
registerOperators(key, operator) {
this.jsonLogic[key] = operator;
}
getOperator(key) {
return this.jsonLogic[key];
}
}

View File

@ -9,13 +9,11 @@
/* globals define,module */
import dayjs from 'dayjs';
/*
Using a Universal Module Loader that should be browser, require, and AMD friendly
http://ricostacruz.com/cheatsheets/umdjs.html
*/
export function getJsonLogic() {
export function getOperators() {
'use strict';
/* globals console:false */
@ -359,12 +357,12 @@ export function getJsonLogic() {
return !!value;
};
jsonLogic.get_operator = function (logic) {
jsonLogic.getOperator = function (logic) {
return Object.keys(logic)[0];
};
jsonLogic.get_values = function (logic) {
return logic[jsonLogic.get_operator(logic)];
jsonLogic.getValues = function (logic) {
return logic[jsonLogic.getOperator(logic)];
};
jsonLogic.apply = function (logic, data) {
@ -379,7 +377,7 @@ export function getJsonLogic() {
return logic;
}
var op = jsonLogic.get_operator(logic);
var op = jsonLogic.getOperator(logic);
var values = logic[op];
var i;
var current;
@ -543,7 +541,7 @@ export function getJsonLogic() {
var collection = [];
if (jsonLogic.is_logic(logic)) {
var op = jsonLogic.get_operator(logic);
var op = jsonLogic.getOperator(logic);
var values = logic[op];
if (!Array.isArray(values)) {
@ -564,11 +562,11 @@ export function getJsonLogic() {
return arrayUnique(collection);
};
jsonLogic.add_operation = function (name, code) {
jsonLogic.addOperation = function (name, code) {
operations[name] = code;
};
jsonLogic.rm_operation = function (name) {
jsonLogic.rmOperation = function (name) {
delete operations[name];
};
@ -593,8 +591,8 @@ export function getJsonLogic() {
if (jsonLogic.is_logic(pattern)) {
if (jsonLogic.is_logic(rule)) {
var pattern_op = jsonLogic.get_operator(pattern);
var rule_op = jsonLogic.get_operator(rule);
var pattern_op = jsonLogic.getOperator(pattern);
var rule_op = jsonLogic.getOperator(rule);
if (pattern_op === '@' || pattern_op === rule_op) {
// echo "\nOperators match, go deeper\n";

View File

@ -47,6 +47,7 @@ import { ActionContextProvider } from './context';
import { useGetAriaLabelOfAction } from './hooks/useGetAriaLabelOfAction';
import { ActionContextProps, ActionProps, ComposedAction } from './types';
import { linkageAction, setInitialActionState } from './utils';
import { useApp } from '../../../application';
const useA = () => {
return {
@ -95,7 +96,7 @@ export const Action: ComposedAction = withDynamicSchemaProps(
const { setSubmitted } = useActionContext();
const { getAriaLabel } = useGetAriaLabelOfAction(title);
const parentRecordData = useCollectionParentRecordData();
const app = useApp();
useEffect(() => {
if (field.stateOfLinkageRules) {
setInitialActionState(field);
@ -105,13 +106,16 @@ export const Action: ComposedAction = withDynamicSchemaProps(
.filter((k) => !k.disabled)
.forEach((v) => {
v.actions?.forEach((h) => {
linkageAction({
linkageAction(
{
operator: h.operator,
field,
condition: v.condition,
variables,
localVariables,
});
},
app.jsonLogic,
);
});
});
}, [field, linkageRules, localVariables, variables]);

View File

@ -80,25 +80,28 @@ export const requestSettingsSchema: ISchema = {
},
};
export const linkageAction = async ({
export const linkageAction = async (
{
operator,
field,
condition,
variables,
localVariables,
}: {
}: {
operator;
field;
condition;
variables: VariablesContextType;
localVariables: VariableOption[];
}) => {
},
jsonLogic: any,
) => {
const disableResult = field?.stateOfLinkageRules?.disabled || [false];
const displayResult = field?.stateOfLinkageRules?.display || ['visible'];
switch (operator) {
case ActionType.Visible:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) {
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
displayResult.push(operator);
field.data = field.data || {};
field.data.hidden = false;
@ -110,7 +113,7 @@ export const linkageAction = async ({
field.display = last(displayResult);
break;
case ActionType.Hidden:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) {
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
field.data = field.data || {};
field.data.hidden = true;
} else {
@ -119,7 +122,7 @@ export const linkageAction = async ({
}
break;
case ActionType.Disabled:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) {
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
disableResult.push(true);
}
field.stateOfLinkageRules = {
@ -130,7 +133,7 @@ export const linkageAction = async ({
field.componentProps['disabled'] = last(disableResult);
break;
case ActionType.Active:
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables })) {
if (await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic)) {
disableResult.push(false);
} else {
disableResult.push(!!field.componentProps?.['disabled']);

View File

@ -16,6 +16,7 @@ import { forEachLinkageRule } from '../../../../schema-settings/LinkageRules/for
import useLocalVariables from '../../../../variables/hooks/useLocalVariables';
import useVariables from '../../../../variables/hooks/useVariables';
import { useSubFormValue } from '../../association-field/hooks';
import { useApp } from '../../../../application';
import { isSubMode } from '../../association-field/util';
const isSubFormOrSubTableField = (fieldSchema: Schema) => {
@ -45,6 +46,7 @@ export const useLinkageRulesForSubTableOrSubForm = () => {
const variables = useVariables();
const linkageRules = getLinkageRules(schemaOfSubTableOrSubForm);
const app = useApp();
useEffect(() => {
if (!isSubFormOrSubTableField(fieldSchema)) {
@ -77,7 +79,8 @@ export const useLinkageRulesForSubTableOrSubForm = () => {
forEachLinkageRule(linkageRules, (action, rule) => {
if (action.targetFields?.includes(fieldSchema.name)) {
disposes.push(
bindLinkageRulesToFiled({
bindLinkageRulesToFiled(
{
field,
linkageRules,
formValues: formValue,
@ -86,7 +89,9 @@ export const useLinkageRulesForSubTableOrSubForm = () => {
rule,
variables,
variableNameOfLeftCondition: '$iteration',
}),
},
app.jsonLogic,
),
);
}
});

View File

@ -27,6 +27,7 @@ import { useToken } from '../../../style';
import { useLocalVariables, useVariables } from '../../../variables';
import { useProps } from '../../hooks/useProps';
import { useFormBlockHeight } from './hook';
import { useApp } from '../../../application';
export interface FormProps extends IFormLayoutProps {
form?: FormilyForm;
@ -136,6 +137,7 @@ const WithForm = (props: WithFormProps) => {
const localVariables = useLocalVariables({ currentForm: form });
const { templateFinished } = useTemplateBlockContext();
const { loading } = useDataBlockRequest() || {};
const app = useApp();
const linkageRules: any[] =
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
@ -175,7 +177,8 @@ const WithForm = (props: WithFormProps) => {
// 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `reaction` 代替
onFieldInit(`*(${fields})`, (field: any, form) => {
disposes.push(
bindLinkageRulesToFiled({
bindLinkageRulesToFiled(
{
field,
linkageRules,
formValues: form.values,
@ -183,7 +186,9 @@ const WithForm = (props: WithFormProps) => {
action,
rule,
variables,
}),
},
app.jsonLogic,
),
);
});
}

View File

@ -14,7 +14,7 @@ import { VariableOption, VariablesContextType } from '../../../variables/types';
import { isVariable } from '../../../variables/utils/isVariable';
import { transformVariableValue } from '../../../variables/utils/transformVariableValue';
import { inferPickerType } from '../../antd/date-picker/util';
import { getJsonLogic } from '../../common/utils/logic';
type VariablesCtx = {
/** 当前登录的用户 */
$user?: Record<string, any>;
@ -76,12 +76,13 @@ function getAllKeys(obj) {
return keys;
}
export const conditionAnalyses = async ({
export const conditionAnalyses = async (
{
ruleGroup,
variables,
localVariables,
variableNameOfLeftCondition,
}: {
}: {
ruleGroup;
variables: VariablesContextType;
localVariables: VariableOption[];
@ -90,17 +91,19 @@ export const conditionAnalyses = async ({
* @default '$nForm'
*/
variableNameOfLeftCondition?: string;
}) => {
},
jsonLogic: any,
) => {
const type = Object.keys(ruleGroup)[0] || '$and';
const conditions = ruleGroup[type];
let results = conditions.map(async (condition) => {
if ('$and' in condition || '$or' in condition) {
return await conditionAnalyses({ ruleGroup: condition, variables, localVariables });
return await conditionAnalyses({ ruleGroup: condition, variables, localVariables }, jsonLogic);
}
const jsonlogic = getInnermostKeyAndValue(condition);
const operator = jsonlogic?.key;
const logicCalculation = getInnermostKeyAndValue(condition);
const operator = logicCalculation?.key;
if (!operator) {
return true;
@ -113,12 +116,11 @@ export const conditionAnalyses = async ({
})
.then(({ value }) => value);
const parsingResult = isVariable(jsonlogic?.value)
? [variables.parseVariable(jsonlogic?.value, localVariables).then(({ value }) => value), targetValue]
: [jsonlogic?.value, targetValue];
const parsingResult = isVariable(logicCalculation?.value)
? [variables.parseVariable(logicCalculation?.value, localVariables).then(({ value }) => value), targetValue]
: [logicCalculation?.value, targetValue];
try {
const jsonLogic = getJsonLogic();
const [value, targetValue] = await Promise.all(parsingResult);
const targetCollectionField = await variables.getCollectionField(targetVariableName, localVariables);
let currentInputValue = transformVariableValue(targetValue, { targetCollectionField });

View File

@ -22,6 +22,7 @@ import { linkageAction } from '../../schema-component/antd/action/utils';
import { usePopupUtils } from '../../schema-component/antd/page/pagePopupUtils';
import { parseVariables } from '../../schema-component/common/utils/uitls';
import { useLocalVariables, useVariables } from '../../variables';
import { useApp } from '../../application';
export function useAclCheck(actionPath) {
const aclCheck = useAclCheckFn();
@ -73,6 +74,7 @@ const InternalCreateRecordAction = (props: any, ref) => {
const { openPopup } = usePopupUtils();
const treeRecordData = useTreeParentRecord();
const cm = useCollectionManager();
const app = useApp();
useEffect(() => {
field.stateOfLinkageRules = {};
@ -80,13 +82,16 @@ const InternalCreateRecordAction = (props: any, ref) => {
.filter((k) => !k.disabled)
.forEach((v) => {
v.actions?.forEach((h) => {
linkageAction({
linkageAction(
{
operator: h.operator,
field,
condition: v.condition,
variables,
localVariables,
});
},
app.jsonLogic,
);
});
});
}, [field, linkageRules, localVariables, variables]);
@ -143,7 +148,6 @@ export const CreateAction = observer(
const form = useForm();
const variables = useVariables();
const aclCheck = useAclCheckFn();
const enableChildren = fieldSchema['x-enable-children'] || [];
const allowAddToCurrent = fieldSchema?.['x-allow-add-to-current'];
const linkageFromForm = fieldSchema?.['x-component-props']?.['linkageFromForm'];
@ -176,6 +180,7 @@ export const CreateAction = observer(
const compile = useCompile();
const { designable } = useDesignable();
const icon = props.icon || null;
const app = useApp();
const menuItems = useMemo<MenuProps['items']>(() => {
return inheritsCollections.map((option) => ({
key: option.name,
@ -196,13 +201,16 @@ export const CreateAction = observer(
.filter((k) => !k.disabled)
.forEach((v) => {
v.actions?.forEach((h) => {
linkageAction({
linkageAction(
{
operator: h.operator,
field,
condition: v.condition,
variables,
localVariables,
});
},
app.jsonLogic,
);
});
});
}, [field, linkageRules, localVariables, variables]);

View File

@ -39,7 +39,8 @@ interface Props {
variableNameOfLeftCondition?: string;
}
export function bindLinkageRulesToFiled({
export function bindLinkageRulesToFiled(
{
field,
linkageRules,
formValues,
@ -48,7 +49,7 @@ export function bindLinkageRulesToFiled({
rule,
variables,
variableNameOfLeftCondition,
}: {
}: {
field: any;
linkageRules: any[];
formValues: any;
@ -61,7 +62,9 @@ export function bindLinkageRulesToFiled({
* @default '$nForm'
*/
variableNameOfLeftCondition?: string;
}) {
},
jsonLogic: any,
) {
field['initStateOfLinkageRules'] = {
display: field.initStateOfLinkageRules?.display || getTempFieldState(true, field.display),
required: field.initStateOfLinkageRules?.required || getTempFieldState(true, field.required || false),
@ -89,7 +92,7 @@ export function bindLinkageRulesToFiled({
.join(',');
return result;
},
getSubscriber({ action, field, rule, variables, localVariables, variableNameOfLeftCondition }),
getSubscriber({ action, field, rule, variables, localVariables, variableNameOfLeftCondition }, jsonLogic),
{ fireImmediately: true, equals: _.isEqual },
);
}
@ -176,14 +179,15 @@ function getVariableValue(variableString: string, localVariables: VariableOption
return getValuesByPath(ctx, getPath(variableString));
}
function getSubscriber({
function getSubscriber(
{
action,
field,
rule,
variables,
localVariables,
variableNameOfLeftCondition,
}: {
}: {
action: any;
field: any;
rule: any;
@ -194,10 +198,13 @@ function getSubscriber({
* @default '$nForm'
*/
variableNameOfLeftCondition?: string;
}): (value: string, oldValue: string) => void {
},
jsonLogic,
): (value: string, oldValue: string) => void {
return () => {
// 当条件改变触发 reaction 时,会同步收集字段状态,并保存到 field.stateOfLinkageRules 中
collectFieldStateOfLinkageRules({
collectFieldStateOfLinkageRules(
{
operator: action.operator,
value: action.value,
field,
@ -205,7 +212,9 @@ function getSubscriber({
variables,
localVariables,
variableNameOfLeftCondition,
});
},
jsonLogic,
);
// 当条件改变时,有可能会触发多个 reaction所以这里需要延迟一下确保所有的 reaction 都执行完毕后,
// 再从 field.stateOfLinkageRules 中取值,因为此时 field.stateOfLinkageRules 中的值才是全的。
@ -286,15 +295,10 @@ function getFieldNameByOperator(operator: ActionType) {
}
}
export const collectFieldStateOfLinkageRules = ({
operator,
value,
field,
condition,
variables,
localVariables,
variableNameOfLeftCondition,
}: Props) => {
export const collectFieldStateOfLinkageRules = (
{ operator, value, field, condition, variables, localVariables, variableNameOfLeftCondition }: Props,
jsonLogic: any,
) => {
const requiredResult = field?.stateOfLinkageRules?.required || [field?.initStateOfLinkageRules?.required];
const displayResult = field?.stateOfLinkageRules?.display || [field?.initStateOfLinkageRules?.display];
const patternResult = field?.stateOfLinkageRules?.pattern || [field?.initStateOfLinkageRules?.pattern];
@ -304,14 +308,14 @@ export const collectFieldStateOfLinkageRules = ({
switch (operator) {
case ActionType.Required:
requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), true));
requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), true));
field.stateOfLinkageRules = {
...field.stateOfLinkageRules,
required: requiredResult,
};
break;
case ActionType.InRequired:
requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), false));
requiredResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), false));
field.stateOfLinkageRules = {
...field.stateOfLinkageRules,
required: requiredResult,
@ -320,7 +324,7 @@ export const collectFieldStateOfLinkageRules = ({
case ActionType.Visible:
case ActionType.None:
case ActionType.Hidden:
displayResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), operator));
displayResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), operator));
field.stateOfLinkageRules = {
...field.stateOfLinkageRules,
display: displayResult,
@ -329,7 +333,7 @@ export const collectFieldStateOfLinkageRules = ({
case ActionType.Editable:
case ActionType.ReadOnly:
case ActionType.ReadPretty:
patternResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), operator));
patternResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), operator));
field.stateOfLinkageRules = {
...field.stateOfLinkageRules,
pattern: patternResult,
@ -364,7 +368,7 @@ export const collectFieldStateOfLinkageRules = ({
if (isConditionEmpty(condition)) {
valueResult.push(getTempFieldState(true, getValue()));
} else {
valueResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult), getValue()));
valueResult.push(getTempFieldState(conditionAnalyses(paramsToGetConditionResult, jsonLogic), getValue()));
}
field.stateOfLinkageRules = {
...field.stateOfLinkageRules,

View File

@ -25,13 +25,13 @@ const getActionValue = (operator, value) => {
}
};
const getSatisfiedActions = async ({ rules, variables, localVariables }) => {
const getSatisfiedActions = async ({ rules, variables, localVariables }, jsonLogic) => {
const satisfiedRules = (
await Promise.all(
rules
.filter((k) => !k.disabled)
.map(async (rule) => {
if (await conditionAnalyses({ ruleGroup: rule.condition, variables, localVariables })) {
if (await conditionAnalyses({ ruleGroup: rule.condition, variables, localVariables }, jsonLogic)) {
return rule;
} else return null;
}),
@ -40,15 +40,15 @@ const getSatisfiedActions = async ({ rules, variables, localVariables }) => {
return satisfiedRules.map((rule) => rule.actions).flat();
};
const getSatisfiedValues = async ({ rules, variables, localVariables }) => {
return (await getSatisfiedActions({ rules, variables, localVariables })).map((action) => ({
const getSatisfiedValues = async ({ rules, variables, localVariables }, jsonLogic) => {
return (await getSatisfiedActions({ rules, variables, localVariables }, jsonLogic)).map((action) => ({
...action,
value: getActionValue(action.operator, action.value),
}));
};
export const getSatisfiedValueMap = async ({ rules, variables, localVariables }) => {
const values = await getSatisfiedValues({ rules, variables, localVariables });
export const getSatisfiedValueMap = async ({ rules, variables, localVariables }, jsonLogic) => {
const values = await getSatisfiedValues({ rules, variables, localVariables }, jsonLogic);
const valueMap = values.reduce((a, v) => ({ ...a, [v.operator]: v.value }), {});
return valueMap;
};

View File

@ -15,7 +15,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { useLocalVariables, useVariables } from '../../variables';
import { getSatisfiedValueMap } from './compute-rules';
import { LinkageRuleCategory, LinkageRuleDataKeyMap } from './type';
import { useApp } from '../../application';
export function useSatisfiedActionValues({
formValues,
category = 'default',
@ -35,10 +35,11 @@ export function useSatisfiedActionValues({
const localVariables = useLocalVariables({ currentForm: { values: formValues } as any });
const localSchema = schema ?? fieldSchema;
const styleRules = rules ?? localSchema[LinkageRuleDataKeyMap[category]];
const app = useApp();
const compute = useCallback(() => {
if (styleRules && formValues) {
getSatisfiedValueMap({ rules: styleRules, variables, localVariables })
getSatisfiedValueMap({ rules: styleRules, variables, localVariables }, app.jsonLogic)
.then((valueMap) => {
if (!isEmpty(valueMap)) {
setValueMap(valueMap);

View File

@ -121,9 +121,11 @@ const RuleTypes = {
number: t('Number', { ns: NAMESPACE }),
lowercase: t('Lowercase letters', { ns: NAMESPACE }),
uppercase: t('Uppercase letters', { ns: NAMESPACE }),
symbol: t('Symbols', { ns: NAMESPACE })
symbol: t('Symbols', { ns: NAMESPACE }),
};
return <code>{value?.map(charset => charsetLabels[charset]).join(', ') || t('Number', { ns: NAMESPACE })}</code>;
return (
<code>{value?.map((charset) => charsetLabels[charset]).join(', ') || t('Number', { ns: NAMESPACE })}</code>
);
},
},
fieldset: {
@ -154,14 +156,14 @@ const RuleTypes = {
{ value: 'number', label: `{{t("Number", { ns: "${NAMESPACE}" })}}` },
{ value: 'lowercase', label: `{{t("Lowercase letters", { ns: "${NAMESPACE}" })}}` },
{ value: 'uppercase', label: `{{t("Uppercase letters", { ns: "${NAMESPACE}" })}}` },
{ value: 'symbol', label: `{{t("Symbols", { ns: "${NAMESPACE}" })}}` }
{ value: 'symbol', label: `{{t("Symbols", { ns: "${NAMESPACE}" })}}` },
],
required: true,
default: ['number'],
'x-validator': {
minItems: 1,
message: `{{t("At least one character set should be selected", { ns: "${NAMESPACE}" })}}`
}
message: `{{t("At least one character set should be selected", { ns: "${NAMESPACE}" })}}`,
},
},
},
defaults: {

View File

@ -301,7 +301,7 @@ const CHAR_SETS = {
lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
// 符号只保留常用且安全的符号,有需要的可以自己加比如[]{}|;:,.<>放在链接或者文件名里容易出问题的字符
symbol: '!@#$%^&*_-+'
symbol: '!@#$%^&*_-+',
} as const;
interface RandomCharOptions {
@ -317,21 +317,16 @@ sequencePatterns.register('randomChar', {
if (!options?.charsets || options.charsets.length === 0) {
return 'At least one character set should be selected';
}
if (options.charsets.some(charset => !CHAR_SETS[charset])) {
if (options.charsets.some((charset) => !CHAR_SETS[charset])) {
return 'Invalid charset selected';
}
return null;
},
generate(instance: any, options: RandomCharOptions) {
const {
length = 6,
charsets = ['number']
} = options;
const { length = 6, charsets = ['number'] } = options;
const chars = [...new Set(
charsets.reduce((acc, charset) => acc + CHAR_SETS[charset], '')
)];
const chars = [...new Set(charsets.reduce((acc, charset) => acc + CHAR_SETS[charset], ''))];
const getRandomChar = () => {
const randomIndex = Math.floor(Math.random() * chars.length);
@ -352,20 +347,27 @@ sequencePatterns.register('randomChar', {
},
getMatcher(options: RandomCharOptions) {
const pattern = [...new Set(
const pattern = [
...new Set(
(options.charsets || ['number']).reduce((acc, charset) => {
switch (charset) {
case 'number': return acc + '0-9';
case 'lowercase': return acc + 'a-z';
case 'uppercase': return acc + 'A-Z';
case 'symbol': return acc + CHAR_SETS.symbol.replace('-', '').replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + '-';
default: return acc;
case 'number':
return acc + '0-9';
case 'lowercase':
return acc + 'a-z';
case 'uppercase':
return acc + 'A-Z';
case 'symbol':
return acc + CHAR_SETS.symbol.replace('-', '').replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') + '-';
default:
return acc;
}
}, '')
)].join('');
}, ''),
),
].join('');
return `[${pattern}]{${options.length || 6}}`;
}
},
});
interface PatternConfig {