Skip to content

Commit f5c4ab1

Browse files
sharevbCorentinTh
andauthored
feat(new tool): Regex Tester (and Cheatsheet) (#1030)
* feat(new tool): Regex Tester Fix #1007, #991, #936, #761, #649 #644, #554 #308 * fix: refactor to service + add regex diagram + ui enhancements * fix: update queryParams * fix: deps * fix: svg style bug in @regexper/render @regexper/render use a stylesheet in svg that cause bugs in whole site. So add regexper in a shadow root * feat(new tool): added Regex Cheatsheet * Update src/tools/regex-memo/index.ts * Update src/tools/regex-tester/index.ts --------- Co-authored-by: Corentin THOMASSET <[email protected]>
1 parent 6709498 commit f5c4ab1

14 files changed

+650
-11
lines changed

Diff for: components.d.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -130,23 +130,19 @@ declare module '@vue/runtime-core' {
130130
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
131131
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
132132
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
133-
NButton: typeof import('naive-ui')['NButton']
134-
NCode: typeof import('naive-ui')['NCode']
133+
NCheckbox: typeof import('naive-ui')['NCheckbox']
135134
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
136135
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
136+
NDivider: typeof import('naive-ui')['NDivider']
137137
NEllipsis: typeof import('naive-ui')['NEllipsis']
138-
NForm: typeof import('naive-ui')['NForm']
139-
NFormItem: typeof import('naive-ui')['NFormItem']
140138
NH1: typeof import('naive-ui')['NH1']
141139
NH3: typeof import('naive-ui')['NH3']
142140
NIcon: typeof import('naive-ui')['NIcon']
143-
NInputNumber: typeof import('naive-ui')['NInputNumber']
144141
NLayout: typeof import('naive-ui')['NLayout']
145142
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
146143
NMenu: typeof import('naive-ui')['NMenu']
147-
NScrollbar: typeof import('naive-ui')['NScrollbar']
148-
NSlider: typeof import('naive-ui')['NSlider']
149-
NSwitch: typeof import('naive-ui')['NSwitch']
144+
NSpace: typeof import('naive-ui')['NSpace']
145+
NTable: typeof import('naive-ui')['NTable']
150146
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
151147
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
152148
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
@@ -156,6 +152,9 @@ declare module '@vue/runtime-core' {
156152
PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default']
157153
QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default']
158154
RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default']
155+
RegexMemo: typeof import('./src/tools/regex-memo/regex-memo.vue')['default']
156+
'RegexMemo.content': typeof import('./src/tools/regex-memo/regex-memo.content.md')['default']
157+
RegexTester: typeof import('./src/tools/regex-tester/regex-tester.vue')['default']
159158
ResultRow: typeof import('./src/tools/ipv4-range-expander/result-row.vue')['default']
160159
RomanNumeralConverter: typeof import('./src/tools/roman-numeral-converter/roman-numeral-converter.vue')['default']
161160
RouterLink: typeof import('vue-router')['RouterLink']

Diff for: package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@
3737
"dependencies": {
3838
"@it-tools/bip39": "^0.0.4",
3939
"@it-tools/oggen": "^1.3.0",
40+
"@regexper/render": "^1.0.0",
4041
"@sindresorhus/slugify": "^2.2.1",
4142
"@tiptap/pm": "2.1.6",
4243
"@tiptap/starter-kit": "2.1.6",
4344
"@tiptap/vue-3": "2.0.3",
44-
"@types/markdown-it": "^13.0.7",
4545
"@types/figlet": "^1.5.8",
46+
"@types/markdown-it": "^13.0.7",
4647
"@vicons/material": "^0.12.0",
4748
"@vicons/tabler": "^0.12.0",
4849
"@vueuse/core": "^10.3.0",
@@ -84,6 +85,7 @@
8485
"pinia": "^2.0.34",
8586
"plausible-tracker": "^0.3.8",
8687
"qrcode": "^1.5.1",
88+
"randexp": "^0.5.3",
8789
"sql-formatter": "^13.0.0",
8890
"ua-parser-js": "^1.0.35",
8991
"ulid": "^2.3.0",
@@ -93,6 +95,7 @@
9395
"vue": "^3.3.4",
9496
"vue-i18n": "^9.9.1",
9597
"vue-router": "^4.1.6",
98+
"vue-shadow-dom": "^4.2.0",
9699
"vue-tsc": "^1.8.1",
97100
"xml-formatter": "^3.3.2",
98101
"xml-js": "^1.6.11",

Diff for: pnpm-lock.yaml

+46
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/composable/queryParams.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { useRouteQuery } from '@vueuse/router';
22
import { computed } from 'vue';
3+
import { useStorage } from '@vueuse/core';
34

4-
export { useQueryParam };
5+
export { useQueryParam, useQueryParamOrStorage };
56

67
const transformers = {
78
number: {
@@ -16,6 +17,12 @@ const transformers = {
1617
fromQuery: (value: string) => value.toLowerCase() === 'true',
1718
toQuery: (value: boolean) => (value ? 'true' : 'false'),
1819
},
20+
object: {
21+
fromQuery: (value: string) => {
22+
return JSON.parse(value);
23+
},
24+
toQuery: (value: object) => JSON.stringify(value),
25+
},
1926
};
2027

2128
function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
@@ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
3340
},
3441
});
3542
}
43+
44+
function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
45+
const type = typeof defaultValue;
46+
const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
47+
48+
const storageRef = useStorage(storageName, defaultValue);
49+
const proxyDefaultValue = transformer.toQuery(defaultValue as never);
50+
const proxy = useRouteQuery(name, proxyDefaultValue);
51+
52+
const r = ref(defaultValue);
53+
54+
watch(r,
55+
(value) => {
56+
proxy.value = transformer.toQuery(value as never);
57+
storageRef.value = value as never;
58+
},
59+
{ deep: true });
60+
61+
r.value = (proxy.value && proxy.value !== proxyDefaultValue
62+
? transformer.fromQuery(proxy.value) as unknown as T
63+
: storageRef.value as T) as never;
64+
65+
return r;
66+
}

Diff for: src/composable/validation.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import _ from 'lodash';
33
import { type Ref, reactive, watch } from 'vue';
44

55
type ValidatorReturnType = unknown;
6+
type GetErrorMessageReturnType = string;
67

78
export interface UseValidationRule<T> {
89
validator: (value: T) => ValidatorReturnType
10+
getErrorMessage?: (value: T) => GetErrorMessageReturnType
911
message: string
1012
}
1113

@@ -24,6 +26,15 @@ export function isFalsyOrHasThrown(cb: () => ValidatorReturnType): boolean {
2426
}
2527
}
2628

29+
export function getErrorMessageOrThrown(cb: () => GetErrorMessageReturnType): string {
30+
try {
31+
return cb() || '';
32+
}
33+
catch (e: any) {
34+
return e.toString();
35+
}
36+
}
37+
2738
export interface ValidationAttrs {
2839
feedback: string
2940
validationStatus: string | undefined
@@ -61,7 +72,13 @@ export function useValidation<T>({
6172

6273
for (const rule of get(rules)) {
6374
if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
64-
state.message = rule.message;
75+
if (rule.getErrorMessage) {
76+
const getErrorMessage = rule.getErrorMessage;
77+
state.message = rule.message.replace('{0}', getErrorMessageOrThrown(() => getErrorMessage(source.value)));
78+
}
79+
else {
80+
state.message = rule.message;
81+
}
6582
state.status = 'error';
6683
}
6784
}

Diff for: src/main.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createPinia } from 'pinia';
33
import { createHead } from '@vueuse/head';
44

55
import { registerSW } from 'virtual:pwa-register';
6+
import shadow from 'vue-shadow-dom';
67
import { plausible } from './plugins/plausible.plugin';
78

89
import 'virtual:uno.css';
@@ -23,5 +24,6 @@ app.use(i18nPlugin);
2324
app.use(router);
2425
app.use(naive);
2526
app.use(plausible);
27+
app.use(shadow);
2628

2729
app.mount('#app');

Diff for: src/tools/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { tool as textToUnicode } from './text-to-unicode';
99
import { tool as safelinkDecoder } from './safelink-decoder';
1010
import { tool as xmlToJson } from './xml-to-json';
1111
import { tool as jsonToXml } from './json-to-xml';
12+
import { tool as regexTester } from './regex-tester';
13+
import { tool as regexMemo } from './regex-memo';
1214
import { tool as markdownToHtml } from './markdown-to-html';
1315
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
1416
import { tool as numeronymGenerator } from './numeronym-generator';
@@ -156,6 +158,8 @@ export const toolsByCategory: ToolCategory[] = [
156158
xmlFormatter,
157159
yamlViewer,
158160
emailNormalizer,
161+
regexTester,
162+
regexMemo,
159163
],
160164
},
161165
{

Diff for: src/tools/regex-memo/index.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { BrandJavascript } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'Regex cheatsheet',
6+
path: '/regex-memo',
7+
description: 'Javascript Regex/Regular Expression cheatsheet',
8+
keywords: ['regex', 'regular', 'expression', 'javascript', 'memo', 'cheatsheet'],
9+
component: () => import('./regex-memo.vue'),
10+
icon: BrandJavascript,
11+
createdAt: new Date('2024-09-20'),
12+
});

0 commit comments

Comments
 (0)