feat: 优化内容模板变量引用与换行展示
- 内容模板直接引用上游参数并自动维护运行参数 - 支持 Enjoy 变量标签编辑及中文变量显示 - 保留试运行与聊天运行页的模板输出换行
This commit is contained in:
@@ -5,6 +5,8 @@ import {
|
||||
findBackspaceTokenRange,
|
||||
findTokenRangeAtCursor,
|
||||
flattenParameterNames,
|
||||
formatParamToken,
|
||||
getTokenRanges,
|
||||
insertTextAtCursor,
|
||||
parseTokenParts,
|
||||
splitTokenDisplay,
|
||||
@@ -64,18 +66,21 @@ describe('paramToken utils', () => {
|
||||
expect(result).toEqual([
|
||||
{
|
||||
dataType: undefined,
|
||||
disconnected: false,
|
||||
displayName: 'input',
|
||||
name: 'input',
|
||||
resolved: false,
|
||||
},
|
||||
{
|
||||
dataType: undefined,
|
||||
disconnected: false,
|
||||
displayName: 'docs',
|
||||
name: 'docs',
|
||||
resolved: true,
|
||||
},
|
||||
{
|
||||
dataType: undefined,
|
||||
disconnected: false,
|
||||
displayName: 'runtimeInput',
|
||||
name: 'runtimeInput',
|
||||
resolved: true,
|
||||
@@ -178,4 +183,20 @@ describe('paramToken utils', () => {
|
||||
expect(rightBoundary?.key).toBe('docs');
|
||||
expect(leftBoundary?.key).toBe('docs');
|
||||
});
|
||||
|
||||
it('should format and parse Enjoy parameter tokens', () => {
|
||||
expect(formatParamToken('申请人', 'enjoy')).toBe('#(申请人)');
|
||||
expect(getTokenRanges('申请:#(申请人)', 'enjoy')).toEqual([
|
||||
{
|
||||
start: 3,
|
||||
end: 9,
|
||||
text: '#(申请人)',
|
||||
key: '申请人',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should keep complex Enjoy expressions as editable text', () => {
|
||||
expect(getTokenRanges('#(申请人 ?? 被申请人)', 'enjoy')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,22 @@ export interface ParameterCandidate {
|
||||
dataType?: string;
|
||||
}
|
||||
|
||||
const TOKEN_PATTERN = /\{\{\s*([^{}]+?)\s*}}/g;
|
||||
export type TokenSyntax = 'enjoy' | 'mustache';
|
||||
|
||||
const MUSTACHE_TOKEN_PATTERN = /\{\{\s*([^{}]+?)\s*}}/g;
|
||||
const ENJOY_TOKEN_PATTERN =
|
||||
/#\(\s*([A-Za-z_$\u4E00-\u9FA5][A-Za-z0-9_$\u4E00-\u9FA5]*(?:\.[A-Za-z_$\u4E00-\u9FA5][A-Za-z0-9_$\u4E00-\u9FA5]*)*)\s*\)/g;
|
||||
|
||||
function getTokenPattern(syntax: TokenSyntax): RegExp {
|
||||
return syntax === 'enjoy' ? ENJOY_TOKEN_PATTERN : MUSTACHE_TOKEN_PATTERN;
|
||||
}
|
||||
|
||||
export function formatParamToken(
|
||||
tokenKey: string,
|
||||
syntax: TokenSyntax = 'mustache',
|
||||
): string {
|
||||
return syntax === 'enjoy' ? `#(${tokenKey})` : `{{${tokenKey}}}`;
|
||||
}
|
||||
|
||||
export function normalizeTokenKey(tokenKey: string): string {
|
||||
return tokenKey.trim();
|
||||
@@ -156,14 +171,16 @@ export function flattenParameterCandidates(
|
||||
export function parseTokenParts(
|
||||
content: string,
|
||||
validParams: string[] = [],
|
||||
syntax: TokenSyntax = 'mustache',
|
||||
): TokenPart[] {
|
||||
const source = content ?? '';
|
||||
const validSet = new Set(validParams.map(normalizeTokenKey));
|
||||
const parts: TokenPart[] = [];
|
||||
const tokenPattern = getTokenPattern(syntax);
|
||||
|
||||
let lastIndex = 0;
|
||||
TOKEN_PATTERN.lastIndex = 0;
|
||||
let match: RegExpExecArray | null = TOKEN_PATTERN.exec(source);
|
||||
tokenPattern.lastIndex = 0;
|
||||
let match: RegExpExecArray | null = tokenPattern.exec(source);
|
||||
|
||||
while (match) {
|
||||
if (match.index > lastIndex) {
|
||||
@@ -183,7 +200,7 @@ export function parseTokenParts(
|
||||
});
|
||||
|
||||
lastIndex = match.index + rawToken.length;
|
||||
match = TOKEN_PATTERN.exec(source);
|
||||
match = tokenPattern.exec(source);
|
||||
}
|
||||
|
||||
if (lastIndex < source.length) {
|
||||
@@ -203,11 +220,15 @@ export function parseTokenParts(
|
||||
return parts;
|
||||
}
|
||||
|
||||
export function getTokenRanges(content: string): TokenRange[] {
|
||||
export function getTokenRanges(
|
||||
content: string,
|
||||
syntax: TokenSyntax = 'mustache',
|
||||
): TokenRange[] {
|
||||
const source = content ?? '';
|
||||
const ranges: TokenRange[] = [];
|
||||
TOKEN_PATTERN.lastIndex = 0;
|
||||
let match: RegExpExecArray | null = TOKEN_PATTERN.exec(source);
|
||||
const tokenPattern = getTokenPattern(syntax);
|
||||
tokenPattern.lastIndex = 0;
|
||||
let match: RegExpExecArray | null = tokenPattern.exec(source);
|
||||
|
||||
while (match) {
|
||||
const rawToken = match[0];
|
||||
@@ -217,7 +238,7 @@ export function getTokenRanges(content: string): TokenRange[] {
|
||||
text: rawToken,
|
||||
key: normalizeTokenKey(match[1] || ''),
|
||||
});
|
||||
match = TOKEN_PATTERN.exec(source);
|
||||
match = tokenPattern.exec(source);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
@@ -229,6 +250,7 @@ export function findTokenRangeAtCursor(
|
||||
options?: {
|
||||
includeStart?: boolean;
|
||||
includeEnd?: boolean;
|
||||
syntax?: TokenSyntax;
|
||||
},
|
||||
): TokenRange | null {
|
||||
if (!Number.isInteger(cursor) || cursor < 0) {
|
||||
@@ -237,7 +259,7 @@ export function findTokenRangeAtCursor(
|
||||
|
||||
const includeStart = options?.includeStart ?? false;
|
||||
const includeEnd = options?.includeEnd ?? false;
|
||||
const ranges = getTokenRanges(content);
|
||||
const ranges = getTokenRanges(content, options?.syntax);
|
||||
for (const range of ranges) {
|
||||
const leftValid = includeStart
|
||||
? cursor >= range.start
|
||||
@@ -254,10 +276,12 @@ export function findTokenRangeAtCursor(
|
||||
export function findBackspaceTokenRange(
|
||||
content: string,
|
||||
cursor: number,
|
||||
syntax: TokenSyntax = 'mustache',
|
||||
): TokenRange | null {
|
||||
return findTokenRangeAtCursor(content, cursor, {
|
||||
includeStart: false,
|
||||
includeEnd: true,
|
||||
syntax,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user