@valten/regulex-visualizer 1.0.8 → 1.0.9
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/package.json +17 -16
- package/index.d.ts +0 -10
- package/src/assets/regulex.js +0 -27
- package/src/components/RegulexVisualizer.vue +0 -486
- package/src/index.js +0 -2
- package/src/test/regulex.html +0 -366
- package/src/test/regulex_vue.html +0 -357
- package/src/test/style.css +0 -208
- /package/dist/{index.mjs → index.js} +0 -0
- /package/dist/{index.umd.js → index.umd.cjs} +0 -0
|
@@ -1,486 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="visualizer-container">
|
|
3
|
-
<div class="visualizer-content">
|
|
4
|
-
<!-- 只保留组件内部的正则输入框 -->
|
|
5
|
-
<div class="visualizer-regex-input code">
|
|
6
|
-
<table>
|
|
7
|
-
<tbody>
|
|
8
|
-
<tr>
|
|
9
|
-
<td style="width:1em">/</td>
|
|
10
|
-
<td>
|
|
11
|
-
<input
|
|
12
|
-
v-model="regexInput"
|
|
13
|
-
class="input"
|
|
14
|
-
@keyup="onKeyup"
|
|
15
|
-
@keydown="onEnter"
|
|
16
|
-
placeholder="输入正则表达式"
|
|
17
|
-
/>
|
|
18
|
-
</td>
|
|
19
|
-
<td style="width:1em">/</td>
|
|
20
|
-
<td style="width:3em">{{ flags }}</td>
|
|
21
|
-
</tr>
|
|
22
|
-
</tbody>
|
|
23
|
-
</table>
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
<!-- 标志选项 -->
|
|
27
|
-
<div class="visualizer-flag-options">
|
|
28
|
-
<label class="visualizer-flag-label">
|
|
29
|
-
<input type="checkbox" v-model="flagI" @change="onFlagChange"/>IgnoreCase
|
|
30
|
-
</label>
|
|
31
|
-
<label class="visualizer-flag-label">
|
|
32
|
-
<input type="checkbox" v-model="flagM" @change="onFlagChange"/>Multiline
|
|
33
|
-
</label>
|
|
34
|
-
<label class="visualizer-flag-label">
|
|
35
|
-
<input type="checkbox" v-model="flagG" @change="onFlagChange"/>GlobalMatch
|
|
36
|
-
</label>
|
|
37
|
-
</div>
|
|
38
|
-
<!-- 错误信息 -->
|
|
39
|
-
<p v-if="errorMessage" class="visualizer-error-box code">{{ errorMessage }}</p>
|
|
40
|
-
<!-- 可视化图形容器 -->
|
|
41
|
-
<div ref="graphContainer" class="visualizer-graph-container code"></div>
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
</template>
|
|
45
|
-
|
|
46
|
-
<script setup>
|
|
47
|
-
import { ref, computed, onMounted, nextTick, watch } from 'vue';
|
|
48
|
-
import regulexScript from '../assets/regulex.js?url';
|
|
49
|
-
|
|
50
|
-
const props = defineProps({
|
|
51
|
-
pattern: {
|
|
52
|
-
type: String,
|
|
53
|
-
default: ''
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const emit = defineEmits(['update:pattern']);
|
|
58
|
-
|
|
59
|
-
// 响应式数据
|
|
60
|
-
const regexInput = ref(props.pattern || '^1[3-9]\\d{9}$');
|
|
61
|
-
const flagI = ref(false);
|
|
62
|
-
const flagM = ref(false);
|
|
63
|
-
const flagG = ref(false);
|
|
64
|
-
const errorMessage = ref('');
|
|
65
|
-
const graphContainer = ref(null);
|
|
66
|
-
const paper = ref(null);
|
|
67
|
-
const regulexDeps = ref(null);
|
|
68
|
-
const params = ref({
|
|
69
|
-
embed: false,
|
|
70
|
-
re: '',
|
|
71
|
-
highlight: true,
|
|
72
|
-
flags: '',
|
|
73
|
-
debug: false
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// 计算属性
|
|
77
|
-
const flags = computed(() => {
|
|
78
|
-
let result = '';
|
|
79
|
-
if (flagI.value) result += 'i';
|
|
80
|
-
if (flagM.value) result += 'm';
|
|
81
|
-
if (flagG.value) result += 'g';
|
|
82
|
-
return result;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// 双向绑定:输入框变化时 emit
|
|
86
|
-
watch(regexInput, (val) => {
|
|
87
|
-
emit('update:pattern', `/${val}/${flags.value}`);
|
|
88
|
-
visualize();
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// 父组件 pattern 变化时同步到输入框
|
|
92
|
-
watch(() => props.pattern, (val) => {
|
|
93
|
-
if (val !== regexInput.value) parseFullRegexString(val);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// 工具函数
|
|
97
|
-
function trim(s) {
|
|
98
|
-
return s.replace(/^\s+/, '').replace(/\s+$/, '');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 辅助函数:从完整的正则表达式字符串(如 /regex/flags)中解析标志并更新状态
|
|
102
|
-
function parseFullRegexString(fullString) {
|
|
103
|
-
const match = fullString.match(/^\/(.*)\/([gimsuy]*)$/);
|
|
104
|
-
if (match) {
|
|
105
|
-
regexInput.value = match[1];
|
|
106
|
-
const flagsStr = match[2];
|
|
107
|
-
flagI.value = flagsStr.includes('i');
|
|
108
|
-
flagM.value = flagsStr.includes('m');
|
|
109
|
-
flagG.value = flagsStr.includes('g');
|
|
110
|
-
} else {
|
|
111
|
-
// 如果不是 /.../flags 格式,则清除标志并返回原始字符串
|
|
112
|
-
regexInput.value = fullString;
|
|
113
|
-
flagI.value = false;
|
|
114
|
-
flagM.value = false;
|
|
115
|
-
flagG.value = false;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function getParams() {
|
|
120
|
-
const hash = location.hash;
|
|
121
|
-
if (!hash || hash.length < 2) {
|
|
122
|
-
return { embed: false, re: "", highlight: true, flags: '' };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const paramsStr = hash.slice(2);
|
|
126
|
-
const paramsObj = paramsStr.split("&").reduce((p, a) => {
|
|
127
|
-
const parts = a.split("=");
|
|
128
|
-
p[parts[0]] = parts[1];
|
|
129
|
-
return p;
|
|
130
|
-
}, {});
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
embed: paramsObj.embed === 'true',
|
|
134
|
-
flags: paramsObj.flags || '',
|
|
135
|
-
re: paramsObj.re ? trim(decodeURIComponent(paramsObj.re)) : '',
|
|
136
|
-
debug: paramsObj.debug === 'true',
|
|
137
|
-
cmd: paramsObj.cmd || ''
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function serializeHash() {
|
|
142
|
-
const re = trim(regexInput.value);
|
|
143
|
-
const flagsStr = flags.value;
|
|
144
|
-
return "#!" +
|
|
145
|
-
(params.value.debug ? "debug=true&" : "") +
|
|
146
|
-
(params.value.embed ? "embed=true&" : "") +
|
|
147
|
-
"flags=" + flagsStr + "&re=" + encodeURIComponent(re);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function changeHash() {
|
|
151
|
-
location.hash = serializeHash();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function hideError() {
|
|
155
|
-
errorMessage.value = '';
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function showError(re, err) {
|
|
159
|
-
let msg = ["Error:" + err.message, ""];
|
|
160
|
-
if (typeof err.lastIndex === 'number') {
|
|
161
|
-
msg.push(re);
|
|
162
|
-
msg.push('-'.repeat(err.lastIndex) + "^");
|
|
163
|
-
}
|
|
164
|
-
errorMessage.value = msg.join("\n");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// 获取 regulex 依赖
|
|
168
|
-
function getRegulexDependencies() {
|
|
169
|
-
try {
|
|
170
|
-
if (typeof window !== 'undefined') {
|
|
171
|
-
if (window.require) {
|
|
172
|
-
try {
|
|
173
|
-
const regulex = window.require('regulex');
|
|
174
|
-
if (regulex) {
|
|
175
|
-
return {
|
|
176
|
-
parse: regulex.parse,
|
|
177
|
-
visualize: regulex.visualize,
|
|
178
|
-
Raphael: regulex.Raphael,
|
|
179
|
-
Kit: regulex.Kit
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
} catch (e) {
|
|
183
|
-
console.error('Require method failed:', e);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return null;
|
|
188
|
-
} catch (error) {
|
|
189
|
-
console.error('Failed to load Regulex dependencies:', error);
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 动态加载 regulex 脚本
|
|
195
|
-
function loadRegulexScript() {
|
|
196
|
-
return new Promise((resolve, reject) => {
|
|
197
|
-
if (window.regulexLoaded) {
|
|
198
|
-
const deps = getRegulexDependencies();
|
|
199
|
-
if (deps) {
|
|
200
|
-
resolve(deps);
|
|
201
|
-
} else {
|
|
202
|
-
reject(new Error('Regulex not found after previous load'));
|
|
203
|
-
}
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const script = document.createElement('script');
|
|
208
|
-
script.type = 'text/javascript';
|
|
209
|
-
script.src = regulexScript;
|
|
210
|
-
|
|
211
|
-
script.onload = () => {
|
|
212
|
-
window.regulexLoaded = true;
|
|
213
|
-
const deps = getRegulexDependencies();
|
|
214
|
-
if (deps) {
|
|
215
|
-
resolve(deps);
|
|
216
|
-
} else {
|
|
217
|
-
reject(new Error('Failed to load Regulex dependencies'));
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
script.onerror = () => {
|
|
222
|
-
reject(new Error('Failed to load Regulex script'));
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
document.head.appendChild(script);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// 核心可视化函数
|
|
230
|
-
function visualize(skipError = false) {
|
|
231
|
-
if (!regulexDeps.value) {
|
|
232
|
-
console.error('Regulex dependencies not available');
|
|
233
|
-
return false;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (!graphContainer.value) {
|
|
237
|
-
console.error('Graph container not available');
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const re = trim(regexInput.value);
|
|
242
|
-
changeHash();
|
|
243
|
-
hideError();
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
// 只在第一次创建 paper,后续重复使用
|
|
247
|
-
if (!paper.value) {
|
|
248
|
-
paper.value = regulexDeps.value.Raphael(graphContainer.value, 10, 10);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 解析正则表达式
|
|
252
|
-
const ast = regulexDeps.value.parse(re);
|
|
253
|
-
|
|
254
|
-
// 可视化
|
|
255
|
-
regulexDeps.value.visualize(ast, flags.value, paper.value);
|
|
256
|
-
return true;
|
|
257
|
-
} catch (e) {
|
|
258
|
-
if (!skipError) {
|
|
259
|
-
showError(re, e);
|
|
260
|
-
}
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 事件处理函数
|
|
266
|
-
function onFlagChange() {
|
|
267
|
-
visualize();
|
|
268
|
-
changeHash();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function onKeyup(e) {
|
|
272
|
-
if (e.keyCode === 13) return; // Enter
|
|
273
|
-
clearTimeout(window.onKeyupTid);
|
|
274
|
-
window.onKeyupTid = setTimeout(() => {
|
|
275
|
-
visualize();
|
|
276
|
-
}, 100);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function onEnter(e) {
|
|
280
|
-
if (e.keyCode === 13) {
|
|
281
|
-
e.preventDefault();
|
|
282
|
-
e.stopPropagation();
|
|
283
|
-
visualize();
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// 拖拽功能
|
|
288
|
-
function dragGraph(container) {
|
|
289
|
-
if (!container) return;
|
|
290
|
-
|
|
291
|
-
container.addEventListener('mousedown', startMove);
|
|
292
|
-
|
|
293
|
-
function startMove(e) {
|
|
294
|
-
clearSelect();
|
|
295
|
-
let x = e.clientX, y = e.clientY;
|
|
296
|
-
container.addEventListener('mousemove', onMove);
|
|
297
|
-
|
|
298
|
-
document.addEventListener('mouseup', unbind, true);
|
|
299
|
-
window.addEventListener('mouseup', unbind, true);
|
|
300
|
-
|
|
301
|
-
function unbind(e) {
|
|
302
|
-
container.removeEventListener('mousemove', onMove);
|
|
303
|
-
document.removeEventListener('mouseup', unbind, true);
|
|
304
|
-
window.removeEventListener('mouseup', unbind, true);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function onMove(e) {
|
|
308
|
-
let dx = x - e.clientX, dy = y - e.clientY;
|
|
309
|
-
if (dx > 0 && container.scrollWidth - container.scrollLeft - container.clientWidth < 2
|
|
310
|
-
|| dx < 0 && container.scrollLeft < 1) {
|
|
311
|
-
document.documentElement.scrollLeft += dx;
|
|
312
|
-
document.body.scrollLeft += dx;
|
|
313
|
-
} else {
|
|
314
|
-
container.scrollLeft += dx;
|
|
315
|
-
}
|
|
316
|
-
if (dy > 0 && container.scrollHeight - container.scrollTop - container.clientHeight < 2
|
|
317
|
-
|| dy < 0 && container.scrollTop < 1) {
|
|
318
|
-
document.documentElement.scrollTop += dy;
|
|
319
|
-
document.body.scrollTop += dy;
|
|
320
|
-
} else {
|
|
321
|
-
container.scrollTop += dy;
|
|
322
|
-
}
|
|
323
|
-
x = e.clientX;
|
|
324
|
-
y = e.clientY;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function clearSelect() {
|
|
330
|
-
if (window.getSelection) {
|
|
331
|
-
if (window.getSelection().empty) {
|
|
332
|
-
window.getSelection().empty();
|
|
333
|
-
} else if (window.getSelection()?.removeAllRanges) {
|
|
334
|
-
window.getSelection()?.removeAllRanges();
|
|
335
|
-
}
|
|
336
|
-
} else if (document.selection) {
|
|
337
|
-
document.selection.empty();
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// 组件挂载时初始化
|
|
342
|
-
onMounted(async () => {
|
|
343
|
-
try {
|
|
344
|
-
// 获取 regulex 依赖
|
|
345
|
-
regulexDeps.value = getRegulexDependencies();
|
|
346
|
-
|
|
347
|
-
// 如果同步加载失败,尝试异步加载
|
|
348
|
-
if (!regulexDeps.value) {
|
|
349
|
-
regulexDeps.value = await loadRegulexScript();
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (!regulexDeps.value) {
|
|
353
|
-
errorMessage.value = 'Error: Failed to load Regulex dependencies';
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// 获取URL参数
|
|
358
|
-
const urlParams = getParams();
|
|
359
|
-
Object.assign(params.value, urlParams);
|
|
360
|
-
|
|
361
|
-
// 设置初始值,优先使用 props 中的值,并解析标志
|
|
362
|
-
if (props.pattern) {
|
|
363
|
-
// parseFullRegexString 会同时更新标志状态
|
|
364
|
-
parseFullRegexString(props.pattern);
|
|
365
|
-
} else if (params.value.re) {
|
|
366
|
-
parseFullRegexString(params.value.re);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// 如果 URL 参数中明确指定了 flags,则覆盖之前解析到的标志
|
|
370
|
-
if (params.value.flags) {
|
|
371
|
-
flagI.value = params.value.flags.includes('i');
|
|
372
|
-
flagM.value = params.value.flags.includes('m');
|
|
373
|
-
flagG.value = params.value.flags.includes('g');
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// 等待 DOM 渲染完成后再初始化
|
|
377
|
-
await nextTick();
|
|
378
|
-
visualize();
|
|
379
|
-
if (graphContainer.value) {
|
|
380
|
-
dragGraph(graphContainer.value);
|
|
381
|
-
}
|
|
382
|
-
} catch (err) {
|
|
383
|
-
errorMessage.value = 'Error: Failed to load Regulex dependencies';
|
|
384
|
-
console.error('Failed to initialize RegexVisualizer:', err);
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
</script>
|
|
388
|
-
|
|
389
|
-
<style>
|
|
390
|
-
.visualizer-container {
|
|
391
|
-
min-height: 200px;
|
|
392
|
-
padding: 20px;
|
|
393
|
-
background: #303030;
|
|
394
|
-
position: relative
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
.visualizer-content {
|
|
398
|
-
width: 100%
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
.visualizer-regex-input {
|
|
402
|
-
background: #eee;
|
|
403
|
-
font-size: 1.2em;
|
|
404
|
-
line-height: 1.4em;
|
|
405
|
-
color: #333;
|
|
406
|
-
border: 1px solid black;
|
|
407
|
-
padding: 4px;
|
|
408
|
-
font-weight: 700;
|
|
409
|
-
word-break: break-all;
|
|
410
|
-
word-wrap: break-word
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
.visualizer-regex-input table {
|
|
414
|
-
border: none;
|
|
415
|
-
padding: 0;
|
|
416
|
-
margin: 0;
|
|
417
|
-
width: 100%
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.visualizer-regex-input .input {
|
|
421
|
-
color: #3030c0;
|
|
422
|
-
padding: 0 2px;
|
|
423
|
-
border: none;
|
|
424
|
-
background: #eee;
|
|
425
|
-
font-size: 1.2em;
|
|
426
|
-
line-height: 1.4em;
|
|
427
|
-
height: 1.4em;
|
|
428
|
-
font-weight: 700;
|
|
429
|
-
word-break: break-all;
|
|
430
|
-
word-wrap: break-word;
|
|
431
|
-
margin: 0;
|
|
432
|
-
width: 100%;
|
|
433
|
-
outline: none
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
.visualizer-flag-options {
|
|
437
|
-
margin-top: 8px
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
.visualizer-flag-label {
|
|
441
|
-
color: #fff;
|
|
442
|
-
cursor: pointer;
|
|
443
|
-
display: inline-block;
|
|
444
|
-
margin-right: 15px;
|
|
445
|
-
font-size: 14px
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
.visualizer-flag-label input {
|
|
449
|
-
margin-right: 4px
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
.visualizer-error-box {
|
|
453
|
-
background: #eee;
|
|
454
|
-
font-size: 1.2em;
|
|
455
|
-
line-height: 1.4em;
|
|
456
|
-
border: 1px solid black;
|
|
457
|
-
padding: 4px;
|
|
458
|
-
color: #8b0000;
|
|
459
|
-
white-space: pre;
|
|
460
|
-
word-wrap: normal;
|
|
461
|
-
word-break: keep-all;
|
|
462
|
-
overflow: auto;
|
|
463
|
-
margin: 8px 0
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
.visualizer-graph-container {
|
|
467
|
-
padding: 4px;
|
|
468
|
-
margin: 8px 0;
|
|
469
|
-
border: 1px solid black;
|
|
470
|
-
background: #eee;
|
|
471
|
-
overflow: auto;
|
|
472
|
-
cursor: move;
|
|
473
|
-
min-height: 200px;
|
|
474
|
-
border-radius: 0 0 10px 10px;
|
|
475
|
-
display: flex;
|
|
476
|
-
justify-content: center;
|
|
477
|
-
align-items: flex-start
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
.visualizer-graph-container svg {
|
|
481
|
-
display: block;
|
|
482
|
-
margin: 0 auto;
|
|
483
|
-
max-width: 100%;
|
|
484
|
-
height: auto
|
|
485
|
-
}
|
|
486
|
-
</style>
|
package/src/index.js
DELETED