Merge branch 'main' into feat/hex-file-converter
This commit is contained in:
		
						commit
						8c65ad8ed6
					
				
							
								
								
									
										
											BIN
										
									
								
								.github/logo-dark.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/logo-dark.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 40 KiB | 
							
								
								
									
										
											BIN
										
									
								
								.github/logo-white.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/logo-white.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 39 KiB | 
							
								
								
									
										
											BIN
										
									
								
								.github/logo.png
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.github/logo.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 7.8 KiB | 
| @ -1,4 +1,8 @@ | ||||
|  | ||||
| <picture> | ||||
|     <source srcset="./.github/logo-dark.png" media="(prefers-color-scheme: light)"> | ||||
|     <source srcset="./.github/logo-white.png" media="(prefers-color-scheme: dark)"> | ||||
|     <img src="./.github/logo-dark.png" alt="logo"> | ||||
| </picture> | ||||
| 
 | ||||
| Useful tools for developer and people working in IT. [Have a look !](https://it-tools.tech). | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										6
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -122,6 +122,7 @@ declare module '@vue/runtime-core' { | ||||
|     LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default'] | ||||
|     MacAddressGenerator: typeof import('./src/tools/mac-address-generator/mac-address-generator.vue')['default'] | ||||
|     MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default'] | ||||
|     MarkdownToHtml: typeof import('./src/tools/markdown-to-html/markdown-to-html.vue')['default'] | ||||
|     MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default'] | ||||
|     MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default'] | ||||
|     MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default'] | ||||
| @ -130,7 +131,7 @@ declare module '@vue/runtime-core' { | ||||
|     MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] | ||||
|     MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] | ||||
|     NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] | ||||
|     NCode: typeof import('naive-ui')['NCode'] | ||||
|     NCheckbox: typeof import('naive-ui')['NCheckbox'] | ||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||
|     NDivider: typeof import('naive-ui')['NDivider'] | ||||
| @ -154,6 +155,9 @@ declare module '@vue/runtime-core' { | ||||
|     PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default'] | ||||
|     QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default'] | ||||
|     RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default'] | ||||
|     RegexMemo: typeof import('./src/tools/regex-memo/regex-memo.vue')['default'] | ||||
|     'RegexMemo.content': typeof import('./src/tools/regex-memo/regex-memo.content.md')['default'] | ||||
|     RegexTester: typeof import('./src/tools/regex-tester/regex-tester.vue')['default'] | ||||
|     ResultRow: typeof import('./src/tools/ipv4-range-expander/result-row.vue')['default'] | ||||
|     RomanNumeralConverter: typeof import('./src/tools/roman-numeral-converter/roman-numeral-converter.vue')['default'] | ||||
|     RouterLink: typeof import('vue-router')['RouterLink'] | ||||
|  | ||||
| @ -37,11 +37,13 @@ | ||||
|   "dependencies": { | ||||
|     "@it-tools/bip39": "^0.0.4", | ||||
|     "@it-tools/oggen": "^1.3.0", | ||||
|     "@regexper/render": "^1.0.0", | ||||
|     "@sindresorhus/slugify": "^2.2.1", | ||||
|     "@tiptap/pm": "2.1.6", | ||||
|     "@tiptap/starter-kit": "2.1.6", | ||||
|     "@tiptap/vue-3": "2.0.3", | ||||
|     "@types/figlet": "^1.5.8", | ||||
|     "@types/markdown-it": "^13.0.7", | ||||
|     "@vicons/material": "^0.12.0", | ||||
|     "@vicons/tabler": "^0.12.0", | ||||
|     "@vueuse/core": "^10.3.0", | ||||
| @ -70,6 +72,7 @@ | ||||
|     "jwt-decode": "^3.1.2", | ||||
|     "libphonenumber-js": "^1.10.28", | ||||
|     "lodash": "^4.17.21", | ||||
|     "markdown-it": "^14.0.0", | ||||
|     "marked": "^10.0.0", | ||||
|     "mathjs": "^11.9.1", | ||||
|     "mime-types": "^2.1.35", | ||||
| @ -82,6 +85,7 @@ | ||||
|     "pinia": "^2.0.34", | ||||
|     "plausible-tracker": "^0.3.8", | ||||
|     "qrcode": "^1.5.1", | ||||
|     "randexp": "^0.5.3", | ||||
|     "sql-formatter": "^13.0.0", | ||||
|     "ua-parser-js": "^1.0.35", | ||||
|     "ulid": "^2.3.0", | ||||
| @ -91,6 +95,7 @@ | ||||
|     "vue": "^3.3.4", | ||||
|     "vue-i18n": "^9.9.1", | ||||
|     "vue-router": "^4.1.6", | ||||
|     "vue-shadow-dom": "^4.2.0", | ||||
|     "vue-tsc": "^1.8.1", | ||||
|     "xml-formatter": "^3.3.2", | ||||
|     "xml-js": "^1.6.11", | ||||
|  | ||||
							
								
								
									
										93
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										93
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @ -11,6 +11,9 @@ dependencies: | ||||
|   '@it-tools/oggen': | ||||
|     specifier: ^1.3.0 | ||||
|     version: 1.3.0 | ||||
|   '@regexper/render': | ||||
|     specifier: ^1.0.0 | ||||
|     version: 1.0.0 | ||||
|   '@sindresorhus/slugify': | ||||
|     specifier: ^2.2.1 | ||||
|     version: 2.2.1 | ||||
| @ -26,6 +29,9 @@ dependencies: | ||||
|   '@types/figlet': | ||||
|     specifier: ^1.5.8 | ||||
|     version: 1.5.8 | ||||
|   '@types/markdown-it': | ||||
|     specifier: ^13.0.7 | ||||
|     version: 13.0.9 | ||||
|   '@vicons/material': | ||||
|     specifier: ^0.12.0 | ||||
|     version: 0.12.0 | ||||
| @ -110,6 +116,9 @@ dependencies: | ||||
|   lodash: | ||||
|     specifier: ^4.17.21 | ||||
|     version: 4.17.21 | ||||
|   markdown-it: | ||||
|     specifier: ^14.0.0 | ||||
|     version: 14.1.0 | ||||
|   marked: | ||||
|     specifier: ^10.0.0 | ||||
|     version: 10.0.0 | ||||
| @ -146,6 +155,9 @@ dependencies: | ||||
|   qrcode: | ||||
|     specifier: ^1.5.1 | ||||
|     version: 1.5.1 | ||||
|   randexp: | ||||
|     specifier: ^0.5.3 | ||||
|     version: 0.5.3 | ||||
|   sql-formatter: | ||||
|     specifier: ^13.0.0 | ||||
|     version: 13.0.0 | ||||
| @ -173,6 +185,9 @@ dependencies: | ||||
|   vue-router: | ||||
|     specifier: ^4.1.6 | ||||
|     version: 4.1.6(vue@3.3.4) | ||||
|   vue-shadow-dom: | ||||
|     specifier: ^4.2.0 | ||||
|     version: 4.2.0 | ||||
|   vue-tsc: | ||||
|     specifier: ^1.8.1 | ||||
|     version: 1.8.1(typescript@5.2.2) | ||||
| @ -2477,6 +2492,17 @@ packages: | ||||
|     resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /@regexper/parser@1.0.0: | ||||
|     resolution: {integrity: sha512-S8AWIGpCNdl9PNHdbhI6TpXZsPk6FDU/RTZI+6UFF4rVFqDQKjCIbZSgFu7NihoEZKq57wKFPbbT1EzrjVvPHA==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /@regexper/render@1.0.0: | ||||
|     resolution: {integrity: sha512-xYm9RUgnhhZotTtf8UZpK1PG2CcTRXQ3JPwfTlYUZsy2J+UcTVc7BaO/MJadpMoVuT8jrIyptH4Y0HLzqhI3hQ==} | ||||
|     dependencies: | ||||
|       '@regexper/parser': 1.0.0 | ||||
|       '@svgdotjs/svg.js': 3.2.4 | ||||
|     dev: false | ||||
| 
 | ||||
|   /@remirror/core-constants@2.0.1: | ||||
|     resolution: {integrity: sha512-ZR4aihtnnT9lMbhh5DEbsriJRlukRXmLZe7HmM+6ufJNNUDoazc75UX26xbgQlNUqgAqMcUdGFAnPc1JwgAdLQ==} | ||||
|     dependencies: | ||||
| @ -2641,6 +2667,10 @@ packages: | ||||
|       string.prototype.matchall: 4.0.10 | ||||
|     dev: true | ||||
| 
 | ||||
|   /@svgdotjs/svg.js@3.2.4: | ||||
|     resolution: {integrity: sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /@tiptap/core@2.1.12(@tiptap/pm@2.1.6): | ||||
|     resolution: {integrity: sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==} | ||||
|     peerDependencies: | ||||
| @ -2970,7 +3000,6 @@ packages: | ||||
| 
 | ||||
|   /@types/linkify-it@3.0.2: | ||||
|     resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} | ||||
|     dev: true | ||||
| 
 | ||||
|   /@types/lodash-es@4.17.10: | ||||
|     resolution: {integrity: sha512-YJP+w/2khSBwbUSFdGsSqmDvmnN3cCKoPOL7Zjle6s30ZtemkkqhjVfFqGwPN7ASil5VyjE2GtyU/yqYY6mC0A==} | ||||
| @ -2992,6 +3021,13 @@ packages: | ||||
|       '@types/mdurl': 1.0.2 | ||||
|     dev: true | ||||
| 
 | ||||
|   /@types/markdown-it@13.0.9: | ||||
|     resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} | ||||
|     dependencies: | ||||
|       '@types/linkify-it': 3.0.2 | ||||
|       '@types/mdurl': 1.0.2 | ||||
|     dev: false | ||||
| 
 | ||||
|   /@types/mdast@3.0.11: | ||||
|     resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} | ||||
|     dependencies: | ||||
| @ -3000,7 +3036,6 @@ packages: | ||||
| 
 | ||||
|   /@types/mdurl@1.0.2: | ||||
|     resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} | ||||
|     dev: true | ||||
| 
 | ||||
|   /@types/mime-types@2.1.1: | ||||
|     resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==} | ||||
| @ -5177,6 +5212,11 @@ packages: | ||||
|       tslib: 2.5.0 | ||||
|     dev: false | ||||
| 
 | ||||
|   /drange@1.1.1: | ||||
|     resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==} | ||||
|     engines: {node: '>=4'} | ||||
|     dev: false | ||||
| 
 | ||||
|   /duplexer@0.1.2: | ||||
|     resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} | ||||
|     dev: true | ||||
| @ -5254,7 +5294,6 @@ packages: | ||||
|   /entities@4.5.0: | ||||
|     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} | ||||
|     engines: {node: '>=0.12'} | ||||
|     dev: true | ||||
| 
 | ||||
|   /errno@0.1.8: | ||||
|     resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} | ||||
| @ -7035,6 +7074,12 @@ packages: | ||||
|     dependencies: | ||||
|       uc.micro: 1.0.6 | ||||
| 
 | ||||
|   /linkify-it@5.0.0: | ||||
|     resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} | ||||
|     dependencies: | ||||
|       uc.micro: 2.1.0 | ||||
|     dev: false | ||||
| 
 | ||||
|   /local-pkg@0.4.3: | ||||
|     resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} | ||||
|     engines: {node: '>=14'} | ||||
| @ -7172,6 +7217,18 @@ packages: | ||||
|       mdurl: 1.0.1 | ||||
|       uc.micro: 1.0.6 | ||||
| 
 | ||||
|   /markdown-it@14.1.0: | ||||
|     resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} | ||||
|     hasBin: true | ||||
|     dependencies: | ||||
|       argparse: 2.0.1 | ||||
|       entities: 4.5.0 | ||||
|       linkify-it: 5.0.0 | ||||
|       mdurl: 2.0.0 | ||||
|       punycode.js: 2.3.1 | ||||
|       uc.micro: 2.1.0 | ||||
|     dev: false | ||||
| 
 | ||||
|   /marked@10.0.0: | ||||
|     resolution: {integrity: sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==} | ||||
|     engines: {node: '>= 18'} | ||||
| @ -7228,6 +7285,10 @@ packages: | ||||
|   /mdurl@1.0.1: | ||||
|     resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} | ||||
| 
 | ||||
|   /mdurl@2.0.0: | ||||
|     resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /merge-stream@2.0.0: | ||||
|     resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} | ||||
|     dev: true | ||||
| @ -8160,6 +8221,11 @@ packages: | ||||
|     resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} | ||||
|     dev: true | ||||
|    | ||||
|   /punycode.js@2.3.1: | ||||
|     resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} | ||||
|     engines: {node: '>=6'} | ||||
|     dev: false | ||||
| 
 | ||||
|   /punycode@2.3.0: | ||||
|     resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} | ||||
|     engines: {node: '>=6'} | ||||
| @ -8211,6 +8277,14 @@ packages: | ||||
|       ret: 0.1.15 | ||||
|     dev: false | ||||
| 
 | ||||
|   /randexp@0.5.3: | ||||
|     resolution: {integrity: sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==} | ||||
|     engines: {node: '>=4'} | ||||
|     dependencies: | ||||
|       drange: 1.1.1 | ||||
|       ret: 0.2.2 | ||||
|     dev: false | ||||
| 
 | ||||
|   /randombytes@2.1.0: | ||||
|     resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} | ||||
|     dependencies: | ||||
| @ -8400,6 +8474,11 @@ packages: | ||||
|     engines: {node: '>=0.12'} | ||||
|     dev: false | ||||
| 
 | ||||
|   /ret@0.2.2: | ||||
|     resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} | ||||
|     engines: {node: '>=4'} | ||||
|     dev: false | ||||
| 
 | ||||
|   /reusify@1.0.4: | ||||
|     resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} | ||||
|     engines: {iojs: '>=1.0.0', node: '>=0.10.0'} | ||||
| @ -9213,6 +9292,10 @@ packages: | ||||
|   /uc.micro@1.0.6: | ||||
|     resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} | ||||
| 
 | ||||
|   /uc.micro@2.1.0: | ||||
|     resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /ufo@1.1.2: | ||||
|     resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} | ||||
| 
 | ||||
| @ -9848,6 +9931,10 @@ packages: | ||||
|       vue: 3.3.4 | ||||
|     dev: false | ||||
| 
 | ||||
|   /vue-shadow-dom@4.2.0: | ||||
|     resolution: {integrity: sha512-lguI064rT2HT/dxqSmXtz860KOvCq+W3nU1jMqroTmX3K1H46q22BMR4emh/Ld3ozy35XJKOaNGcr6mkJ/t/yg==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /vue-template-compiler@2.7.14: | ||||
|     resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} | ||||
|     dependencies: | ||||
|  | ||||
| @ -7,6 +7,7 @@ import sqlHljs from 'highlight.js/lib/languages/sql'; | ||||
| import xmlHljs from 'highlight.js/lib/languages/xml'; | ||||
| import yamlHljs from 'highlight.js/lib/languages/yaml'; | ||||
| import iniHljs from 'highlight.js/lib/languages/ini'; | ||||
| import markdownHljs from 'highlight.js/lib/languages/markdown'; | ||||
| import { useCopy } from '@/composable/copy'; | ||||
| 
 | ||||
| const props = withDefaults( | ||||
| @ -30,6 +31,7 @@ hljs.registerLanguage('html', xmlHljs); | ||||
| hljs.registerLanguage('xml', xmlHljs); | ||||
| hljs.registerLanguage('yaml', yamlHljs); | ||||
| hljs.registerLanguage('toml', iniHljs); | ||||
| hljs.registerLanguage('markdown', markdownHljs); | ||||
| 
 | ||||
| const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props); | ||||
| const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) }; | ||||
|  | ||||
| @ -1,7 +1,8 @@ | ||||
| import { useRouteQuery } from '@vueuse/router'; | ||||
| import { computed } from 'vue'; | ||||
| import { useStorage } from '@vueuse/core'; | ||||
| 
 | ||||
| export { useQueryParam }; | ||||
| export { useQueryParam, useQueryParamOrStorage }; | ||||
| 
 | ||||
| const transformers = { | ||||
|   number: { | ||||
| @ -16,6 +17,12 @@ const transformers = { | ||||
|     fromQuery: (value: string) => value.toLowerCase() === 'true', | ||||
|     toQuery: (value: boolean) => (value ? 'true' : 'false'), | ||||
|   }, | ||||
|   object: { | ||||
|     fromQuery: (value: string) => { | ||||
|       return JSON.parse(value); | ||||
|     }, | ||||
|     toQuery: (value: object) => JSON.stringify(value), | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) { | ||||
| @ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) { | ||||
|   const type = typeof defaultValue; | ||||
|   const transformer = transformers[type as keyof typeof transformers] ?? transformers.string; | ||||
| 
 | ||||
|   const storageRef = useStorage(storageName, defaultValue); | ||||
|   const proxyDefaultValue = transformer.toQuery(defaultValue as never); | ||||
|   const proxy = useRouteQuery(name, proxyDefaultValue); | ||||
| 
 | ||||
|   const r = ref(defaultValue); | ||||
| 
 | ||||
|   watch(r, | ||||
|     (value) => { | ||||
|       proxy.value = transformer.toQuery(value as never); | ||||
|       storageRef.value = value as never; | ||||
|     }, | ||||
|     { deep: true }); | ||||
| 
 | ||||
|   r.value = (proxy.value && proxy.value !== proxyDefaultValue | ||||
|     ? transformer.fromQuery(proxy.value) as unknown as T | ||||
|     : storageRef.value as T) as never; | ||||
| 
 | ||||
|   return r; | ||||
| } | ||||
|  | ||||
| @ -3,9 +3,11 @@ import _ from 'lodash'; | ||||
| import { type Ref, reactive, watch } from 'vue'; | ||||
| 
 | ||||
| type ValidatorReturnType = unknown; | ||||
| type GetErrorMessageReturnType = string; | ||||
| 
 | ||||
| export interface UseValidationRule<T> { | ||||
|   validator: (value: T) => ValidatorReturnType | ||||
|   getErrorMessage?: (value: T) => GetErrorMessageReturnType | ||||
|   message: string | ||||
| } | ||||
| 
 | ||||
| @ -24,6 +26,15 @@ export function isFalsyOrHasThrown(cb: () => ValidatorReturnType): boolean { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function getErrorMessageOrThrown(cb: () => GetErrorMessageReturnType): string { | ||||
|   try { | ||||
|     return cb() || ''; | ||||
|   } | ||||
|   catch (e: any) { | ||||
|     return e.toString(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export interface ValidationAttrs { | ||||
|   feedback: string | ||||
|   validationStatus: string | undefined | ||||
| @ -61,7 +72,13 @@ export function useValidation<T>({ | ||||
| 
 | ||||
|       for (const rule of get(rules)) { | ||||
|         if (isFalsyOrHasThrown(() => rule.validator(source.value))) { | ||||
|           state.message = rule.message; | ||||
|           if (rule.getErrorMessage) { | ||||
|             const getErrorMessage = rule.getErrorMessage; | ||||
|             state.message = rule.message.replace('{0}', getErrorMessageOrThrown(() => getErrorMessage(source.value))); | ||||
|           } | ||||
|           else { | ||||
|             state.message = rule.message; | ||||
|           } | ||||
|           state.status = 'error'; | ||||
|         } | ||||
|       } | ||||
|  | ||||
| @ -3,6 +3,7 @@ import { createPinia } from 'pinia'; | ||||
| import { createHead } from '@vueuse/head'; | ||||
| 
 | ||||
| import { registerSW } from 'virtual:pwa-register'; | ||||
| import shadow from 'vue-shadow-dom'; | ||||
| import { plausible } from './plugins/plausible.plugin'; | ||||
| 
 | ||||
| import 'virtual:uno.css'; | ||||
| @ -23,5 +24,6 @@ app.use(i18nPlugin); | ||||
| app.use(router); | ||||
| app.use(naive); | ||||
| app.use(plausible); | ||||
| app.use(shadow); | ||||
| 
 | ||||
| app.mount('#app'); | ||||
|  | ||||
| @ -10,6 +10,9 @@ import { tool as textToUnicode } from './text-to-unicode'; | ||||
| import { tool as safelinkDecoder } from './safelink-decoder'; | ||||
| import { tool as xmlToJson } from './xml-to-json'; | ||||
| import { tool as jsonToXml } from './json-to-xml'; | ||||
| import { tool as regexTester } from './regex-tester'; | ||||
| import { tool as regexMemo } from './regex-memo'; | ||||
| import { tool as markdownToHtml } from './markdown-to-html'; | ||||
| import { tool as pdfSignatureChecker } from './pdf-signature-checker'; | ||||
| import { tool as numeronymGenerator } from './numeronym-generator'; | ||||
| import { tool as macAddressGenerator } from './mac-address-generator'; | ||||
| @ -114,6 +117,7 @@ export const toolsByCategory: ToolCategory[] = [ | ||||
|       tomlToYaml, | ||||
|       xmlToJson, | ||||
|       jsonToXml, | ||||
|       markdownToHtml, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
| @ -156,6 +160,8 @@ export const toolsByCategory: ToolCategory[] = [ | ||||
|       xmlFormatter, | ||||
|       yamlViewer, | ||||
|       emailNormalizer, | ||||
|       regexTester, | ||||
|       regexMemo, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|  | ||||
							
								
								
									
										12
									
								
								src/tools/markdown-to-html/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/markdown-to-html/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import { Markdown } from '@vicons/tabler'; | ||||
| import { defineTool } from '../tool'; | ||||
| 
 | ||||
| export const tool = defineTool({ | ||||
|   name: 'Markdown to HTML', | ||||
|   path: '/markdown-to-html', | ||||
|   description: 'Convert Markdown to Html and allow to print (as PDF)', | ||||
|   keywords: ['markdown', 'html', 'converter', 'pdf'], | ||||
|   component: () => import('./markdown-to-html.vue'), | ||||
|   icon: Markdown, | ||||
|   createdAt: new Date('2024-08-25'), | ||||
| }); | ||||
							
								
								
									
										44
									
								
								src/tools/markdown-to-html/markdown-to-html.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/tools/markdown-to-html/markdown-to-html.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| <script setup lang="ts"> | ||||
| import markdownit from 'markdown-it'; | ||||
| import TextareaCopyable from '@/components/TextareaCopyable.vue'; | ||||
| 
 | ||||
| const inputMarkdown = ref(''); | ||||
| const outputHtml = computed(() => { | ||||
|   const md = markdownit(); | ||||
|   return md.render(inputMarkdown.value); | ||||
| }); | ||||
| 
 | ||||
| function printHtml() { | ||||
|   const w = window.open(); | ||||
|   if (w === null) { | ||||
|     return; | ||||
|   } | ||||
|   w.document.body.innerHTML = outputHtml.value; | ||||
|   w.print(); | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div> | ||||
|     <c-input-text | ||||
|       v-model:value="inputMarkdown" | ||||
|       multiline raw-text | ||||
|       placeholder="Your Markdown content..." | ||||
|       rows="8" | ||||
|       autofocus | ||||
|       label="Your Markdown to convert:" | ||||
|     /> | ||||
| 
 | ||||
|     <n-divider /> | ||||
| 
 | ||||
|     <n-form-item label="Output HTML:"> | ||||
|       <TextareaCopyable :value="outputHtml" :word-wrap="true" language="html" /> | ||||
|     </n-form-item> | ||||
| 
 | ||||
|     <div flex justify-center> | ||||
|       <n-button @click="printHtml"> | ||||
|         Print as PDF | ||||
|       </n-button> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
							
								
								
									
										12
									
								
								src/tools/regex-memo/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/regex-memo/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import { BrandJavascript } from '@vicons/tabler'; | ||||
| import { defineTool } from '../tool'; | ||||
| 
 | ||||
| export const tool = defineTool({ | ||||
|   name: 'Regex cheatsheet', | ||||
|   path: '/regex-memo', | ||||
|   description: 'Javascript Regex/Regular Expression cheatsheet', | ||||
|   keywords: ['regex', 'regular', 'expression', 'javascript', 'memo', 'cheatsheet'], | ||||
|   component: () => import('./regex-memo.vue'), | ||||
|   icon: BrandJavascript, | ||||
|   createdAt: new Date('2024-09-20'), | ||||
| }); | ||||
							
								
								
									
										121
									
								
								src/tools/regex-memo/regex-memo.content.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/tools/regex-memo/regex-memo.content.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,121 @@ | ||||
| ### Normal characters | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `.` or `[^\n\r]` | any character *excluding* a newline or carriage return | ||||
| `[A-Za-z]` | alphabet | ||||
| `[a-z]` | lowercase alphabet | ||||
| `[A-Z]` | uppercase alphabet | ||||
| `\d` or `[0-9]` | digit | ||||
| `\D` or `[^0-9]` | non-digit | ||||
| `_` | underscore | ||||
| `\w` or `[A-Za-z0-9_]` | alphabet, digit or underscore | ||||
| `\W` or `[^A-Za-z0-9_]` | inverse of `\w` | ||||
| `\S` | inverse of `\s` | ||||
| 
 | ||||
| ### Whitespace characters | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| ` ` | space | ||||
| `\t` | tab | ||||
| `\n` | newline | ||||
| `\r` | carriage return | ||||
| `\s` | space, tab, newline or carriage return | ||||
| 
 | ||||
| ### Character set | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `[xyz]` | either `x`, `y` or `z` | ||||
| `[^xyz]` | neither `x`, `y` nor `z` | ||||
| `[1-3]` | either `1`, `2` or `3` | ||||
| `[^1-3]` | neither `1`, `2` nor `3` | ||||
| 
 | ||||
| - Think of a character set as an `OR` operation on the single characters that are enclosed between the square brackets. | ||||
| - Use `^` after the opening `[` to “negate” the character set. | ||||
| - Within a character set, `.` means a literal period. | ||||
| 
 | ||||
| ### Characters that require escaping | ||||
| 
 | ||||
| #### Outside a character set | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `\.` | period | ||||
| `\^` | caret | ||||
| `\$` | dollar sign | ||||
| `\|` | pipe | ||||
| `\\` | back slash | ||||
| `\/` | forward slash | ||||
| `\(` | opening bracket | ||||
| `\)` | closing bracket | ||||
| `\[` | opening square bracket | ||||
| `\]` | closing square bracket | ||||
| `\{` | opening curly bracket | ||||
| `\}` | closing curly bracket | ||||
| 
 | ||||
| #### Inside a character set | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `\\` | back slash | ||||
| `\]` | closing square bracket | ||||
| 
 | ||||
| - A `^` must be escaped only if it occurs immediately after the opening `[` of the character set. | ||||
| - A `-` must be escaped only if it occurs between two alphabets or two digits. | ||||
| 
 | ||||
| ### Quantifiers | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `{2}` | exactly 2 | ||||
| `{2,}` | at least 2 | ||||
| `{2,7}` | at least 2 but no more than 7 | ||||
| `*` | 0 or more | ||||
| `+` | 1 or more | ||||
| `?` | exactly 0 or 1 | ||||
| 
 | ||||
| - The quantifier goes *after* the expression to be quantified. | ||||
| 
 | ||||
| ### Boundaries | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `^` | start of string | ||||
| `$` | end of string | ||||
| `\b` | word boundary | ||||
| 
 | ||||
| - How word boundary matching works: | ||||
|     - At the beginning of the string if the first character is `\w`. | ||||
|     - Between two adjacent characters within the string, if the first character is `\w` and the second character is `\W`. | ||||
|     - At the end of the string if the last character is `\w`. | ||||
| 
 | ||||
| ### Matching | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `foo\|bar` | match either `foo` or `bar` | ||||
| `foo(?=bar)` | match `foo` if it’s before `bar` | ||||
| `foo(?!bar)` | match `foo` if it’s *not* before `bar` | ||||
| `(?<=bar)foo` | match `foo` if it’s after `bar` | ||||
| `(?<!bar)foo` | match `foo` if it’s *not* after `bar` | ||||
| 
 | ||||
| ### Grouping and capturing | ||||
| 
 | ||||
| Expression | Description | ||||
| :--|:-- | ||||
| `(foo)` | capturing group; match and capture `foo` | ||||
| `(?:foo)` | non-capturing group; match `foo` but *without* capturing `foo` | ||||
| `(foo)bar\1` | `\1` is a backreference to the 1st capturing group; match `foobarfoo` | ||||
| 
 | ||||
| - Capturing groups are only relevant in the following methods: | ||||
|     - `string.match(regexp)` | ||||
|     - `string.matchAll(regexp)` | ||||
|     - `string.replace(regexp, callback)` | ||||
| - `\N` is a backreference to the `Nth` capturing group. Capturing groups are numbered starting from 1. | ||||
| 
 | ||||
| ## References and tools | ||||
| 
 | ||||
| - [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) | ||||
| - [RegExplained](https://leaverou.github.io/regexplained/) | ||||
							
								
								
									
										32
									
								
								src/tools/regex-memo/regex-memo.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/tools/regex-memo/regex-memo.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <script setup lang="ts"> | ||||
| import { useThemeVars } from 'naive-ui'; | ||||
| import Memo from './regex-memo.content.md'; | ||||
| 
 | ||||
| const themeVars = useThemeVars(); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div> | ||||
|     <Memo /> | ||||
|   </div> | ||||
| </template> | ||||
| 
 | ||||
| <style lang="less" scoped> | ||||
| ::v-deep(pre) { | ||||
|   margin: 0; | ||||
|   padding: 15px 22px; | ||||
|   background-color: v-bind('themeVars.cardColor'); | ||||
|   border-radius: 4px; | ||||
|   overflow: auto; | ||||
| } | ||||
| ::v-deep(table) { | ||||
|   border-collapse: collapse; | ||||
| } | ||||
| ::v-deep(table), ::v-deep(td), ::v-deep(th) { | ||||
|   border: 1px solid v-bind('themeVars.textColor1'); | ||||
|   padding: 5px; | ||||
| } | ||||
| ::v-deep(a) { | ||||
|   color: v-bind('themeVars.textColor1'); | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										12
									
								
								src/tools/regex-tester/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/regex-tester/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import { Language } from '@vicons/tabler'; | ||||
| import { defineTool } from '../tool'; | ||||
| 
 | ||||
| export const tool = defineTool({ | ||||
|   name: 'Regex Tester', | ||||
|   path: '/regex-tester', | ||||
|   description: 'Regex Tester', | ||||
|   keywords: ['regex', 'tester', 'sample', 'expression'], | ||||
|   component: () => import('./regex-tester.vue'), | ||||
|   icon: Language, | ||||
|   createdAt: new Date('2024-09-20'), | ||||
| }); | ||||
							
								
								
									
										106
									
								
								src/tools/regex-tester/regex-tester.service.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/tools/regex-tester/regex-tester.service.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,106 @@ | ||||
| import { describe, expect, it } from 'vitest'; | ||||
| import { matchRegex } from './regex-tester.service'; | ||||
| 
 | ||||
| const regexesData = [ | ||||
|   { | ||||
|     regex: '', | ||||
|     text: '', | ||||
|     flags: '', | ||||
|     result: [], | ||||
|   }, | ||||
|   { | ||||
|     regex: '.*', | ||||
|     text: '', | ||||
|     flags: '', | ||||
|     result: [], | ||||
|   }, | ||||
|   { | ||||
|     regex: '', | ||||
|     text: 'aaa', | ||||
|     flags: '', | ||||
|     result: [], | ||||
|   }, | ||||
|   { | ||||
|     regex: 'a', | ||||
|     text: 'baaa', | ||||
|     flags: '', | ||||
|     result: [ | ||||
|       { | ||||
|         captures: [], | ||||
|         groups: [], | ||||
|         index: 1, | ||||
|         value: 'a', | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|     regex: '(.)(?<g>r)', | ||||
|     text: 'azertyr', | ||||
|     flags: 'g', | ||||
|     result: [ | ||||
|       { | ||||
|         captures: [ | ||||
|           { | ||||
|             end: 3, | ||||
|             name: '1', | ||||
|             start: 2, | ||||
|             value: 'e', | ||||
|           }, | ||||
|           { | ||||
|             end: 4, | ||||
|             name: '2', | ||||
|             start: 3, | ||||
|             value: 'r', | ||||
|           }, | ||||
|         ], | ||||
|         groups: [ | ||||
|           { | ||||
|             end: 4, | ||||
|             name: 'g', | ||||
|             start: 3, | ||||
|             value: 'r', | ||||
|           }, | ||||
|         ], | ||||
|         index: 2, | ||||
|         value: 'er', | ||||
|       }, | ||||
|       { | ||||
|         captures: [ | ||||
|           { | ||||
|             end: 6, | ||||
|             name: '1', | ||||
|             start: 5, | ||||
|             value: 'y', | ||||
|           }, | ||||
|           { | ||||
|             end: 7, | ||||
|             name: '2', | ||||
|             start: 6, | ||||
|             value: 'r', | ||||
|           }, | ||||
|         ], | ||||
|         groups: [ | ||||
|           { | ||||
|             end: 7, | ||||
|             name: 'g', | ||||
|             start: 6, | ||||
|             value: 'r', | ||||
|           }, | ||||
|         ], | ||||
|         index: 5, | ||||
|         value: 'yr', | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| ]; | ||||
| 
 | ||||
| describe('regex-tester', () => { | ||||
|   for (const reg of regexesData) { | ||||
|     const { regex, text, flags, result: expected_result } = reg; | ||||
|     it(`Should matchRegex("${regex}","${text}","${flags}") return correct result`, async () => { | ||||
|       const result = matchRegex(regex, text, `${flags}d`); | ||||
| 
 | ||||
|       expect(result).to.deep.equal(expected_result); | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
							
								
								
									
										61
									
								
								src/tools/regex-tester/regex-tester.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/tools/regex-tester/regex-tester.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| interface RegExpGroupIndices { | ||||
|   [name: string]: [number, number] | ||||
| } | ||||
| interface RegExpIndices extends Array<[number, number]> { | ||||
|   groups: RegExpGroupIndices | ||||
| } | ||||
| interface RegExpExecArrayWithIndices extends RegExpExecArray { | ||||
|   indices: RegExpIndices | ||||
| } | ||||
| interface GroupCapture { | ||||
|   name: string | ||||
|   value: string | ||||
|   start: number | ||||
|   end: number | ||||
| }; | ||||
| 
 | ||||
| export function matchRegex(regex: string, text: string, flags: string) { | ||||
|   // if (regex === '' || text === '') {
 | ||||
|   //   return [];
 | ||||
|   // }
 | ||||
| 
 | ||||
|   let lastIndex = -1; | ||||
|   const re = new RegExp(regex, flags); | ||||
|   const results = []; | ||||
|   let match = re.exec(text) as RegExpExecArrayWithIndices; | ||||
|   while (match !== null) { | ||||
|     if (re.lastIndex === lastIndex || match[0] === '') { | ||||
|       break; | ||||
|     } | ||||
|     const indices = match.indices; | ||||
|     const captures: Array<GroupCapture> = []; | ||||
|     Object.entries(match).forEach(([captureName, captureValue]) => { | ||||
|       if (captureName !== '0' && captureName.match(/\d+/)) { | ||||
|         captures.push({ | ||||
|           name: captureName, | ||||
|           value: captureValue, | ||||
|           start: indices[Number(captureName)][0], | ||||
|           end: indices[Number(captureName)][1], | ||||
|         }); | ||||
|       } | ||||
|     }); | ||||
|     const groups: Array<GroupCapture> = []; | ||||
|     Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => { | ||||
|       groups.push({ | ||||
|         name: groupName, | ||||
|         value: groupValue, | ||||
|         start: indices.groups[groupName][0], | ||||
|         end: indices.groups[groupName][1], | ||||
|       }); | ||||
|     }); | ||||
|     results.push({ | ||||
|       index: match.index, | ||||
|       value: match[0], | ||||
|       captures, | ||||
|       groups, | ||||
|     }); | ||||
|     lastIndex = re.lastIndex; | ||||
|     match = re.exec(text) as RegExpExecArrayWithIndices; | ||||
|   } | ||||
|   return results; | ||||
| } | ||||
							
								
								
									
										193
									
								
								src/tools/regex-tester/regex-tester.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/tools/regex-tester/regex-tester.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,193 @@ | ||||
| <script setup lang="ts"> | ||||
| import RandExp from 'randexp'; | ||||
| import { render } from '@regexper/render'; | ||||
| import type { ShadowRootExpose } from 'vue-shadow-dom'; | ||||
| import { matchRegex } from './regex-tester.service'; | ||||
| import { useValidation } from '@/composable/validation'; | ||||
| import { useQueryParamOrStorage } from '@/composable/queryParams'; | ||||
| 
 | ||||
| const regex = useQueryParamOrStorage({ name: 'regex', storageName: 'regex-tester:regex', defaultValue: '' }); | ||||
| const text = ref(''); | ||||
| const global = ref(true); | ||||
| const ignoreCase = ref(false); | ||||
| const multiline = ref(false); | ||||
| const dotAll = ref(true); | ||||
| const unicode = ref(true); | ||||
| const unicodeSets = ref(false); | ||||
| const visualizerSVG = ref<ShadowRootExpose>(); | ||||
| 
 | ||||
| const regexValidation = useValidation({ | ||||
|   source: regex, | ||||
|   rules: [ | ||||
|     { | ||||
|       message: 'Invalid regex: {0}', | ||||
|       validator: value => new RegExp(value), | ||||
|       getErrorMessage: (value) => { | ||||
|         const _ = new RegExp(value); | ||||
|         return ''; | ||||
|       }, | ||||
|     }, | ||||
|   ], | ||||
| }); | ||||
| const results = computed(() => { | ||||
|   let flags = 'd'; | ||||
|   if (global.value) { | ||||
|     flags += 'g'; | ||||
|   } | ||||
|   if (ignoreCase.value) { | ||||
|     flags += 'i'; | ||||
|   } | ||||
|   if (multiline.value) { | ||||
|     flags += 'm'; | ||||
|   } | ||||
|   if (dotAll.value) { | ||||
|     flags += 's'; | ||||
|   } | ||||
|   if (unicode.value) { | ||||
|     flags += 'u'; | ||||
|   } | ||||
|   else if (unicodeSets.value) { | ||||
|     flags += 'v'; | ||||
|   } | ||||
| 
 | ||||
|   try { | ||||
|     return matchRegex(regex.value, text.value, flags); | ||||
|   } | ||||
|   catch (_) { | ||||
|     return []; | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| const sample = computed(() => { | ||||
|   try { | ||||
|     const randexp = new RandExp(new RegExp(regex.value.replace(/\(\?\<[^\>]*\>/g, '(?:'))); | ||||
|     return randexp.gen(); | ||||
|   } | ||||
|   catch (_) { | ||||
|     return ''; | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| watchEffect( | ||||
|   async () => { | ||||
|     const regexValue = regex.value; | ||||
|     // shadow root is required: | ||||
|     // @regexper/render append a <defs><style> that broke svg transparency of icons in the whole site | ||||
|     const visualizer = visualizerSVG.value?.shadow_root; | ||||
|     if (visualizer) { | ||||
|       while (visualizer.lastChild) { | ||||
|         visualizer.removeChild(visualizer.lastChild); | ||||
|       } | ||||
|       const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | ||||
|       try { | ||||
|         await render(regexValue, svg); | ||||
|       } | ||||
|       catch (_) { | ||||
|       } | ||||
|       visualizer.appendChild(svg); | ||||
|     } | ||||
|   }, | ||||
| ); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div max-w-600px> | ||||
|     <c-card title="Regex" mb-1> | ||||
|       <c-input-text | ||||
|         v-model:value="regex" | ||||
|         label="Regex to test:" | ||||
|         placeholder="Put the regex to test" | ||||
|         multiline | ||||
|         rows="3" | ||||
|         :validation="regexValidation" | ||||
|       /> | ||||
|       <router-link target="_blank" to="/regex-memo" mb-1 mt-1> | ||||
|         See Regular Expression Cheatsheet | ||||
|       </router-link> | ||||
|       <n-space> | ||||
|         <n-checkbox v-model:checked="global"> | ||||
|           <span title="Global search">Global search. (<code>g</code>)</span> | ||||
|         </n-checkbox> | ||||
|         <n-checkbox v-model:checked="ignoreCase"> | ||||
|           <span title="Case-insensitive search">Case-insensitive search. (<code>i</code>)</span> | ||||
|         </n-checkbox> | ||||
|         <n-checkbox v-model:checked="multiline"> | ||||
|           <span title="Allows ^ and $ to match next to newline characters.">Multiline(<code>m</code>)</span> | ||||
|         </n-checkbox> | ||||
|         <n-checkbox v-model:checked="dotAll"> | ||||
|           <span title="Allows . to match newline characters.">Singleline(<code>s</code>)</span> | ||||
|         </n-checkbox> | ||||
|         <n-checkbox v-model:checked="unicode"> | ||||
|           <span title="Unicode; treat a pattern as a sequence of Unicode code points.">Unicode(<code>u</code>)</span> | ||||
|         </n-checkbox> | ||||
|         <n-checkbox v-model:checked="unicodeSets"> | ||||
|           <span title="An upgrade to the u mode with more Unicode features.">Unicode Sets (<code>v</code>)</span> | ||||
|         </n-checkbox> | ||||
|       </n-space> | ||||
| 
 | ||||
|       <n-divider /> | ||||
| 
 | ||||
|       <c-input-text | ||||
|         v-model:value="text" | ||||
|         label="Text to match:" | ||||
|         placeholder="Put the text to match" | ||||
|         multiline | ||||
|         rows="5" | ||||
|       /> | ||||
|     </c-card> | ||||
| 
 | ||||
|     <c-card title="Matches" mb-1 mt-3> | ||||
|       <n-table v-if="results?.length > 0"> | ||||
|         <thead> | ||||
|           <tr> | ||||
|             <th scope="col"> | ||||
|               Index in text | ||||
|             </th> | ||||
|             <th scope="col"> | ||||
|               Value | ||||
|             </th> | ||||
|             <th scope="col"> | ||||
|               Captures | ||||
|             </th> | ||||
|             <th scope="col"> | ||||
|               Groups | ||||
|             </th> | ||||
|           </tr> | ||||
|         </thead> | ||||
|         <tbody> | ||||
|           <tr v-for="match of results" :key="match.index"> | ||||
|             <td>{{ match.index }}</td> | ||||
|             <td>{{ match.value }}</td> | ||||
|             <td> | ||||
|               <ul> | ||||
|                 <li v-for="capture in match.captures" :key="capture.name"> | ||||
|                   "{{ capture.name }}" = {{ capture.value }} [{{ capture.start }} - {{ capture.end }}] | ||||
|                 </li> | ||||
|               </ul> | ||||
|             </td> | ||||
|             <td> | ||||
|               <ul> | ||||
|                 <li v-for="group in match.groups" :key="group.name"> | ||||
|                   "{{ group.name }}" = {{ group.value }} [{{ group.start }} - {{ group.end }}] | ||||
|                 </li> | ||||
|               </ul> | ||||
|             </td> | ||||
|           </tr> | ||||
|         </tbody> | ||||
|       </n-table> | ||||
|       <c-alert v-else> | ||||
|         No match | ||||
|       </c-alert> | ||||
|     </c-card> | ||||
| 
 | ||||
|     <c-card title="Sample matching text" mt-3> | ||||
|       <pre style="white-space: pre-wrap; word-break: break-all;">{{ sample }}</pre> | ||||
|     </c-card> | ||||
| 
 | ||||
|     <c-card title="Regex Diagram" style="overflow-x: scroll;" mt-3> | ||||
|       <shadow-root ref="visualizerSVG"> | ||||
|   | ||||
|       </shadow-root> | ||||
|     </c-card> | ||||
|   </div> | ||||
| </template> | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user