@antv/infographic 0.2.15 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/README.zh-CN.md +27 -0
- package/dist/infographic.min.js +130 -129
- package/dist/infographic.min.js.map +1 -1
- package/esm/constants/service.d.ts +1 -1
- package/esm/constants/service.js +1 -1
- package/esm/designs/structures/chart-line.js +2 -1
- package/esm/designs/structures/sequence-interaction.js +36 -15
- package/esm/designs/structures/sequence-timeline.d.ts +1 -0
- package/esm/designs/structures/sequence-timeline.js +4 -2
- package/esm/exporter/png.js +2 -2
- package/esm/exporter/svg.js +176 -2
- package/esm/exporter/types.d.ts +10 -0
- package/esm/options/parser.js +8 -6
- package/esm/options/types.d.ts +3 -3
- package/esm/renderer/renderer.js +1 -1
- package/esm/resource/loaders/search.js +2 -3
- package/esm/runtime/options.js +1 -1
- package/esm/syntax/index.js +56 -10
- package/esm/syntax/mapper.js +20 -6
- package/esm/syntax/parser.js +89 -3
- package/esm/syntax/types.d.ts +1 -1
- package/esm/templates/built-in.js +2 -2
- package/esm/templates/registry.d.ts +1 -0
- package/esm/templates/registry.js +6 -0
- package/esm/templates/utils.d.ts +1 -0
- package/esm/templates/utils.js +63 -0
- package/esm/themes/built-in.js +3 -0
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/lib/constants/service.d.ts +1 -1
- package/lib/constants/service.js +1 -1
- package/lib/designs/structures/chart-line.js +2 -1
- package/lib/designs/structures/sequence-interaction.js +36 -15
- package/lib/designs/structures/sequence-timeline.d.ts +1 -0
- package/lib/designs/structures/sequence-timeline.js +4 -2
- package/lib/exporter/png.js +2 -2
- package/lib/exporter/svg.js +176 -2
- package/lib/exporter/types.d.ts +10 -0
- package/lib/options/parser.js +7 -5
- package/lib/options/types.d.ts +3 -3
- package/lib/renderer/renderer.js +1 -1
- package/lib/resource/loaders/search.js +2 -3
- package/lib/runtime/options.js +1 -1
- package/lib/syntax/index.js +56 -10
- package/lib/syntax/mapper.js +20 -6
- package/lib/syntax/parser.js +89 -3
- package/lib/syntax/types.d.ts +1 -1
- package/lib/templates/built-in.js +2 -2
- package/lib/templates/registry.d.ts +1 -0
- package/lib/templates/registry.js +7 -0
- package/lib/templates/utils.d.ts +1 -0
- package/lib/templates/utils.js +66 -0
- package/lib/themes/built-in.js +3 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/src/constants/service.ts +1 -1
- package/src/designs/structures/chart-line.tsx +3 -1
- package/src/designs/structures/sequence-interaction.tsx +92 -46
- package/src/designs/structures/sequence-timeline.tsx +18 -15
- package/src/exporter/png.ts +3 -2
- package/src/exporter/svg.ts +209 -2
- package/src/exporter/types.ts +10 -0
- package/src/options/parser.ts +7 -6
- package/src/options/types.ts +3 -3
- package/src/renderer/renderer.ts +1 -1
- package/src/resource/loaders/search.ts +2 -2
- package/src/runtime/options.ts +1 -1
- package/src/syntax/index.ts +71 -10
- package/src/syntax/mapper.ts +20 -6
- package/src/syntax/parser.ts +111 -3
- package/src/syntax/types.ts +1 -0
- package/src/templates/built-in.ts +2 -2
- package/src/templates/registry.ts +6 -0
- package/src/templates/utils.ts +87 -0
- package/src/themes/built-in.ts +4 -0
- package/src/version.ts +1 -1
package/src/syntax/index.ts
CHANGED
|
@@ -12,6 +12,16 @@ import {
|
|
|
12
12
|
} from './schema';
|
|
13
13
|
import type { ObjectSchema, SyntaxNode, SyntaxParseResult } from './types';
|
|
14
14
|
|
|
15
|
+
const ALLOWED_ROOT_KEYS = new Set([
|
|
16
|
+
'infographic',
|
|
17
|
+
'template',
|
|
18
|
+
'design',
|
|
19
|
+
'data',
|
|
20
|
+
'theme',
|
|
21
|
+
'width',
|
|
22
|
+
'height',
|
|
23
|
+
]);
|
|
24
|
+
|
|
15
25
|
function normalizeItems(items: ItemDatum[]) {
|
|
16
26
|
const seen = new Set<string>();
|
|
17
27
|
const normalized: ItemDatum[] = [];
|
|
@@ -30,6 +40,14 @@ function normalizeItems(items: ItemDatum[]) {
|
|
|
30
40
|
return normalized.reverse();
|
|
31
41
|
}
|
|
32
42
|
|
|
43
|
+
function assignMissingNodeIds(items: ItemDatum[]) {
|
|
44
|
+
items.forEach((item) => {
|
|
45
|
+
if (!item.id && item.label) {
|
|
46
|
+
item.id = item.label;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
function resolveTemplate(
|
|
34
52
|
node: SyntaxNode | undefined,
|
|
35
53
|
errors: SyntaxParseResult['errors'],
|
|
@@ -44,12 +62,54 @@ function resolveTemplate(
|
|
|
44
62
|
return undefined;
|
|
45
63
|
}
|
|
46
64
|
|
|
65
|
+
function inferTemplateFromBareFirstLine(
|
|
66
|
+
entries: Record<string, SyntaxNode>,
|
|
67
|
+
warnings: SyntaxParseResult['warnings'],
|
|
68
|
+
) {
|
|
69
|
+
if ('infographic' in entries || 'template' in entries) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [firstEntry] = Object.entries(entries);
|
|
74
|
+
if (!firstEntry) return undefined;
|
|
75
|
+
|
|
76
|
+
const [key, node] = firstEntry;
|
|
77
|
+
if (ALLOWED_ROOT_KEYS.has(key) || node.kind !== 'object') {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
if (node.value !== undefined || Object.keys(node.entries).length > 0) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
warnings.push({
|
|
85
|
+
path: 'template',
|
|
86
|
+
line: node.line,
|
|
87
|
+
code: 'implicit_template',
|
|
88
|
+
message:
|
|
89
|
+
'Inferred template from a bare first line. Prefix it with "infographic" or "template" to make the syntax explicit.',
|
|
90
|
+
raw: key,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
template: key,
|
|
95
|
+
inferredKey: key,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
47
99
|
export function parseSyntax(input: string): SyntaxParseResult {
|
|
48
100
|
const { ast, errors } = parseSyntaxToAst(input);
|
|
49
101
|
const warnings: SyntaxParseResult['warnings'] = [];
|
|
50
102
|
const options: Partial<InfographicOptions> = {};
|
|
51
103
|
|
|
52
104
|
const mergedEntries = { ...ast.entries };
|
|
105
|
+
const inferredTemplate = inferTemplateFromBareFirstLine(
|
|
106
|
+
ast.entries,
|
|
107
|
+
warnings,
|
|
108
|
+
);
|
|
109
|
+
if (inferredTemplate) {
|
|
110
|
+
delete mergedEntries[inferredTemplate.inferredKey];
|
|
111
|
+
}
|
|
112
|
+
|
|
53
113
|
const infographicNode = ast.entries.infographic;
|
|
54
114
|
let templateFromInfographic: string | undefined;
|
|
55
115
|
if (infographicNode && infographicNode.kind === 'object') {
|
|
@@ -59,17 +119,8 @@ export function parseSyntax(input: string): SyntaxParseResult {
|
|
|
59
119
|
});
|
|
60
120
|
}
|
|
61
121
|
|
|
62
|
-
const allowedRootKeys = new Set([
|
|
63
|
-
'infographic',
|
|
64
|
-
'template',
|
|
65
|
-
'design',
|
|
66
|
-
'data',
|
|
67
|
-
'theme',
|
|
68
|
-
'width',
|
|
69
|
-
'height',
|
|
70
|
-
]);
|
|
71
122
|
Object.keys(mergedEntries).forEach((key) => {
|
|
72
|
-
if (!
|
|
123
|
+
if (!ALLOWED_ROOT_KEYS.has(key)) {
|
|
73
124
|
errors.push({
|
|
74
125
|
path: key,
|
|
75
126
|
line: (mergedEntries[key] as SyntaxNode).line,
|
|
@@ -86,6 +137,9 @@ export function parseSyntax(input: string): SyntaxParseResult {
|
|
|
86
137
|
if (!options.template && templateFromInfographic) {
|
|
87
138
|
options.template = templateFromInfographic;
|
|
88
139
|
}
|
|
140
|
+
if (!options.template && inferredTemplate) {
|
|
141
|
+
options.template = inferredTemplate.template;
|
|
142
|
+
}
|
|
89
143
|
|
|
90
144
|
const designNode = mergedEntries.design as SyntaxNode | undefined;
|
|
91
145
|
if (designNode) {
|
|
@@ -113,6 +167,13 @@ export function parseSyntax(input: string): SyntaxParseResult {
|
|
|
113
167
|
if (parsed.relations.length > 0 || parsed.items.length > 0) {
|
|
114
168
|
const current = (options.data ?? {}) as Record<string, any>;
|
|
115
169
|
|
|
170
|
+
if (Array.isArray(current.items)) {
|
|
171
|
+
assignMissingNodeIds(current.items as ItemDatum[]);
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(current.nodes)) {
|
|
174
|
+
assignMissingNodeIds(current.nodes as ItemDatum[]);
|
|
175
|
+
}
|
|
176
|
+
|
|
116
177
|
// 优先使用已存在的数据列表 (sequences, lists, etc.)
|
|
117
178
|
const dataKeys = Object.keys(
|
|
118
179
|
(DataSchema as ObjectSchema).fields,
|
package/src/syntax/mapper.ts
CHANGED
|
@@ -16,10 +16,18 @@ const HEX_COLOR_PATTERN =
|
|
|
16
16
|
/^(#[0-9a-f]{8}|#[0-9a-f]{6}|#[0-9a-f]{4}|#[0-9a-f]{3})/i;
|
|
17
17
|
const FUNCTION_COLOR_PATTERN = /^((?:rgb|rgba|hsl|hsla)\([^)]*\))/i;
|
|
18
18
|
|
|
19
|
+
function normalizeBooleanLiteral(value: string) {
|
|
20
|
+
const trimmed = value.trim();
|
|
21
|
+
if (/^true$/i.test(trimmed)) return 'true';
|
|
22
|
+
if (/^false$/i.test(trimmed)) return 'false';
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
function parseScalar(value: string) {
|
|
20
27
|
const trimmed = value.trim();
|
|
21
|
-
|
|
22
|
-
if (
|
|
28
|
+
const normalizedBoolean = normalizeBooleanLiteral(trimmed);
|
|
29
|
+
if (normalizedBoolean === 'true') return true;
|
|
30
|
+
if (normalizedBoolean === 'false') return false;
|
|
23
31
|
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return parseFloat(trimmed);
|
|
24
32
|
return trimmed;
|
|
25
33
|
}
|
|
@@ -240,7 +248,8 @@ export function mapWithSchema(
|
|
|
240
248
|
);
|
|
241
249
|
return undefined;
|
|
242
250
|
}
|
|
243
|
-
|
|
251
|
+
const normalizedBoolean = normalizeBooleanLiteral(value);
|
|
252
|
+
if (!normalizedBoolean) {
|
|
244
253
|
addError(
|
|
245
254
|
errors,
|
|
246
255
|
node,
|
|
@@ -251,7 +260,7 @@ export function mapWithSchema(
|
|
|
251
260
|
);
|
|
252
261
|
return undefined;
|
|
253
262
|
}
|
|
254
|
-
return
|
|
263
|
+
return normalizedBoolean === 'true';
|
|
255
264
|
}
|
|
256
265
|
case 'enum': {
|
|
257
266
|
const value = readScalar(node);
|
|
@@ -259,7 +268,12 @@ export function mapWithSchema(
|
|
|
259
268
|
addError(errors, node, path, 'schema_mismatch', 'Expected enum value.');
|
|
260
269
|
return undefined;
|
|
261
270
|
}
|
|
262
|
-
|
|
271
|
+
const normalizedBoolean = normalizeBooleanLiteral(value);
|
|
272
|
+
const enumValue =
|
|
273
|
+
normalizedBoolean && schema.values.includes(normalizedBoolean)
|
|
274
|
+
? normalizedBoolean
|
|
275
|
+
: value;
|
|
276
|
+
if (!schema.values.includes(enumValue)) {
|
|
263
277
|
addError(
|
|
264
278
|
errors,
|
|
265
279
|
node,
|
|
@@ -270,7 +284,7 @@ export function mapWithSchema(
|
|
|
270
284
|
);
|
|
271
285
|
return undefined;
|
|
272
286
|
}
|
|
273
|
-
return
|
|
287
|
+
return enumValue;
|
|
274
288
|
}
|
|
275
289
|
case 'array': {
|
|
276
290
|
if (node.kind === 'array') {
|
package/src/syntax/parser.ts
CHANGED
|
@@ -40,6 +40,15 @@ function stripComments(content: string) {
|
|
|
40
40
|
return content.trimEnd();
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function isCommentLine(content: string) {
|
|
44
|
+
const trimmed = content.trimStart();
|
|
45
|
+
return trimmed.startsWith('#') || trimmed.startsWith('//');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isCodeFenceLine(content: string) {
|
|
49
|
+
return /^```[\w-]*\s*$/.test(content.trim());
|
|
50
|
+
}
|
|
51
|
+
|
|
43
52
|
function looksLikeRelationExpression(text: string) {
|
|
44
53
|
return /[<>=o.x-]{2,}/.test(text);
|
|
45
54
|
}
|
|
@@ -58,6 +67,97 @@ function parseKeyValue(raw: string) {
|
|
|
58
67
|
return { key: text, value: undefined };
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
interface AssignEntryResult {
|
|
71
|
+
parent: ObjectNode;
|
|
72
|
+
key: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isUnsafeObjectKey(key: string) {
|
|
76
|
+
return key === '__proto__' || key === 'constructor' || key === 'prototype';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function assignObjectEntry(
|
|
80
|
+
parent: ObjectNode,
|
|
81
|
+
rawKey: string,
|
|
82
|
+
node: ObjectNode,
|
|
83
|
+
line: number,
|
|
84
|
+
errors: SyntaxError[],
|
|
85
|
+
): AssignEntryResult | null {
|
|
86
|
+
if (!rawKey.includes('.')) {
|
|
87
|
+
if (isUnsafeObjectKey(rawKey)) {
|
|
88
|
+
errors.push({
|
|
89
|
+
path: rawKey,
|
|
90
|
+
line,
|
|
91
|
+
code: 'bad_syntax',
|
|
92
|
+
message: `Invalid key part: ${rawKey}`,
|
|
93
|
+
raw: rawKey,
|
|
94
|
+
});
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
parent.entries[rawKey] = node;
|
|
98
|
+
return { parent, key: rawKey };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const parts = rawKey.split('.');
|
|
102
|
+
if (parts.some((part) => !part)) {
|
|
103
|
+
errors.push({
|
|
104
|
+
path: rawKey,
|
|
105
|
+
line,
|
|
106
|
+
code: 'bad_syntax',
|
|
107
|
+
message: 'Invalid dotted key path.',
|
|
108
|
+
raw: rawKey,
|
|
109
|
+
});
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let current = parent;
|
|
114
|
+
for (let index = 0; index < parts.length - 1; index += 1) {
|
|
115
|
+
const part = parts[index];
|
|
116
|
+
if (isUnsafeObjectKey(part)) {
|
|
117
|
+
errors.push({
|
|
118
|
+
path: rawKey,
|
|
119
|
+
line,
|
|
120
|
+
code: 'bad_syntax',
|
|
121
|
+
message: `Invalid key part in dotted path: ${part}`,
|
|
122
|
+
raw: rawKey,
|
|
123
|
+
});
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const existing = current.entries[part];
|
|
127
|
+
if (!existing) {
|
|
128
|
+
const container = createObjectNode(line);
|
|
129
|
+
current.entries[part] = container;
|
|
130
|
+
current = container;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (existing.kind !== 'object') {
|
|
134
|
+
errors.push({
|
|
135
|
+
path: parts.slice(0, index + 1).join('.'),
|
|
136
|
+
line,
|
|
137
|
+
code: 'bad_syntax',
|
|
138
|
+
message: 'Cannot assign dotted key under a list value.',
|
|
139
|
+
raw: rawKey,
|
|
140
|
+
});
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
current = existing;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const finalKey = parts[parts.length - 1];
|
|
147
|
+
if (isUnsafeObjectKey(finalKey)) {
|
|
148
|
+
errors.push({
|
|
149
|
+
path: rawKey,
|
|
150
|
+
line,
|
|
151
|
+
code: 'bad_syntax',
|
|
152
|
+
message: `Invalid key part in dotted path: ${finalKey}`,
|
|
153
|
+
raw: rawKey,
|
|
154
|
+
});
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
current.entries[finalKey] = node;
|
|
158
|
+
return { parent: current, key: finalKey };
|
|
159
|
+
}
|
|
160
|
+
|
|
61
161
|
function createObjectNode(line: number, value?: string): ObjectNode {
|
|
62
162
|
return { kind: 'object', line, value, entries: {} };
|
|
63
163
|
}
|
|
@@ -78,6 +178,7 @@ export function parseSyntaxToAst(input: string): ParseResult {
|
|
|
78
178
|
const lineNumber = index + 1;
|
|
79
179
|
if (!line.trim()) return;
|
|
80
180
|
const { indent, content } = getIndentInfo(line);
|
|
181
|
+
if (isCommentLine(content) || isCodeFenceLine(content)) return;
|
|
81
182
|
const stripped = stripComments(content);
|
|
82
183
|
if (!stripped.trim()) return;
|
|
83
184
|
|
|
@@ -203,12 +304,19 @@ export function parseSyntaxToAst(input: string): ParseResult {
|
|
|
203
304
|
}
|
|
204
305
|
|
|
205
306
|
const node = createObjectNode(lineNumber, parsed.value);
|
|
206
|
-
|
|
307
|
+
const assigned = assignObjectEntry(
|
|
308
|
+
parentNode,
|
|
309
|
+
parsed.key,
|
|
310
|
+
node,
|
|
311
|
+
lineNumber,
|
|
312
|
+
errors,
|
|
313
|
+
);
|
|
314
|
+
if (!assigned) return;
|
|
207
315
|
stack.push({
|
|
208
316
|
indent,
|
|
209
317
|
node,
|
|
210
|
-
parent:
|
|
211
|
-
key:
|
|
318
|
+
parent: assigned.parent,
|
|
319
|
+
key: assigned.key,
|
|
212
320
|
});
|
|
213
321
|
});
|
|
214
322
|
|
package/src/syntax/types.ts
CHANGED
|
@@ -210,7 +210,7 @@ const BUILT_IN_TEMPLATES: Record<string, TemplateOptions> = {
|
|
|
210
210
|
'sequence-timeline-plain-text': {
|
|
211
211
|
design: {
|
|
212
212
|
title: 'default',
|
|
213
|
-
structure: { type: 'sequence-timeline' },
|
|
213
|
+
structure: { type: 'sequence-timeline', showStepLabels: false },
|
|
214
214
|
items: [{ type: 'plain-text' }],
|
|
215
215
|
},
|
|
216
216
|
},
|
|
@@ -231,7 +231,7 @@ const BUILT_IN_TEMPLATES: Record<string, TemplateOptions> = {
|
|
|
231
231
|
'sequence-timeline-simple': {
|
|
232
232
|
design: {
|
|
233
233
|
title: 'default',
|
|
234
|
-
structure: { type: 'sequence-timeline', gap: 20 },
|
|
234
|
+
structure: { type: 'sequence-timeline', gap: 20, showStepLabels: false },
|
|
235
235
|
items: [{ type: 'simple', positionV: 'middle' }],
|
|
236
236
|
},
|
|
237
237
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TemplateOptions } from './types';
|
|
2
|
+
import { findClosestTemplateKey } from './utils';
|
|
2
3
|
|
|
3
4
|
const TEMPLATE_REGISTRY = new Map<string, TemplateOptions>();
|
|
4
5
|
|
|
@@ -6,6 +7,11 @@ export function registerTemplate(type: string, template: TemplateOptions) {
|
|
|
6
7
|
TEMPLATE_REGISTRY.set(type, template);
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
export function resolveTemplateKey(type: string): string | undefined {
|
|
11
|
+
if (TEMPLATE_REGISTRY.has(type)) return type;
|
|
12
|
+
return findClosestTemplateKey(type, TEMPLATE_REGISTRY.keys());
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
export function getTemplate(type: string): TemplateOptions | undefined {
|
|
10
16
|
return TEMPLATE_REGISTRY.get(type);
|
|
11
17
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
function normalizeTemplateKey(type: string): string {
|
|
2
|
+
return type
|
|
3
|
+
.trim()
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.replace(/[_\s]+/g, '-')
|
|
6
|
+
.replace(/-+/g, '-');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function getLevenshteinDistance(source: string, target: string): number {
|
|
10
|
+
if (source === target) return 0;
|
|
11
|
+
if (!source.length) return target.length;
|
|
12
|
+
if (!target.length) return source.length;
|
|
13
|
+
|
|
14
|
+
const previous = Array.from(
|
|
15
|
+
{ length: target.length + 1 },
|
|
16
|
+
(_, index) => index,
|
|
17
|
+
);
|
|
18
|
+
const current = new Array<number>(target.length + 1);
|
|
19
|
+
|
|
20
|
+
for (let sourceIndex = 1; sourceIndex <= source.length; sourceIndex += 1) {
|
|
21
|
+
current[0] = sourceIndex;
|
|
22
|
+
const sourceCode = source.charCodeAt(sourceIndex - 1);
|
|
23
|
+
|
|
24
|
+
for (let targetIndex = 1; targetIndex <= target.length; targetIndex += 1) {
|
|
25
|
+
const replaceCost =
|
|
26
|
+
sourceCode === target.charCodeAt(targetIndex - 1) ? 0 : 1;
|
|
27
|
+
current[targetIndex] = Math.min(
|
|
28
|
+
previous[targetIndex] + 1,
|
|
29
|
+
current[targetIndex - 1] + 1,
|
|
30
|
+
previous[targetIndex - 1] + replaceCost,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (let index = 0; index < current.length; index += 1) {
|
|
35
|
+
previous[index] = current[index];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return previous[target.length];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getCommonPrefixLength(source: string, target: string): number {
|
|
43
|
+
const limit = Math.min(source.length, target.length);
|
|
44
|
+
let index = 0;
|
|
45
|
+
|
|
46
|
+
while (index < limit && source[index] === target[index]) {
|
|
47
|
+
index += 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return index;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function findClosestTemplateKey(
|
|
54
|
+
type: string,
|
|
55
|
+
keys: Iterable<string>,
|
|
56
|
+
): string | undefined {
|
|
57
|
+
const normalizedType = normalizeTemplateKey(type);
|
|
58
|
+
if (!normalizedType) return undefined;
|
|
59
|
+
|
|
60
|
+
let bestMatch: string | undefined;
|
|
61
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
62
|
+
let bestPrefixLength = -1;
|
|
63
|
+
|
|
64
|
+
for (const key of keys) {
|
|
65
|
+
const normalizedKey = normalizeTemplateKey(key);
|
|
66
|
+
if (normalizedKey === normalizedType) {
|
|
67
|
+
return key;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const distance = getLevenshteinDistance(normalizedType, normalizedKey);
|
|
71
|
+
const prefixLength = getCommonPrefixLength(normalizedType, normalizedKey);
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
distance < bestDistance ||
|
|
75
|
+
(distance === bestDistance && prefixLength > bestPrefixLength) ||
|
|
76
|
+
(distance === bestDistance &&
|
|
77
|
+
prefixLength === bestPrefixLength &&
|
|
78
|
+
(!bestMatch || key < bestMatch))
|
|
79
|
+
) {
|
|
80
|
+
bestMatch = key;
|
|
81
|
+
bestDistance = distance;
|
|
82
|
+
bestPrefixLength = prefixLength;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return bestMatch;
|
|
87
|
+
}
|
package/src/themes/built-in.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.2.
|
|
1
|
+
export const VERSION = '0.2.17';
|