mirror of
				https://github.com/CorentinTh/it-tools.git
				synced 2025-11-04 05:53:25 +00:00 
			
		
		
		
	Compare commits
	
		
			51 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ba3b84c668 | ||
| 
						 | 
					433ba2a3e5 | ||
| 
						 | 
					8fb0e6af9c | ||
| 
						 | 
					11720e6cde | ||
| 
						 | 
					ac89490794 | ||
| 
						 | 
					2f61c745f5 | ||
| 
						 | 
					a9a6526e75 | ||
| 
						 | 
					af0b02d3a8 | ||
| 
						 | 
					a46d125c19 | ||
| 
						 | 
					6becdbb423 | ||
| 
						 | 
					5ce1262fb4 | ||
| 
						 | 
					d591a73ce7 | ||
| 
						 | 
					a88e4a9289 | ||
| 
						 | 
					d4ea393c1d | ||
| 
						 | 
					048bc4ae94 | ||
| 
						 | 
					3aefe83a31 | ||
| 
						 | 
					c3b6132c26 | ||
| 
						 | 
					69f564e6fe | ||
| 
						 | 
					e9cc499ed8 | ||
| 
						 | 
					383d975695 | ||
| 
						 | 
					347144bfe8 | ||
| 
						 | 
					4c4da16970 | ||
| 
						 | 
					9450537bae | ||
| 
						 | 
					1d7032d026 | ||
| 
						 | 
					d2c767f092 | ||
| 
						 | 
					0cc7af6b1d | ||
| 
						 | 
					34bc6a57a7 | ||
| 
						 | 
					d356b1488f | ||
| 
						 | 
					a60f64f744 | ||
| 
						 | 
					b89db3c8d0 | ||
| 
						 | 
					3cfc5f8bc2 | ||
| 
						 | 
					9da56da096 | ||
| 
						 | 
					9755e51fe2 | ||
| 
						 | 
					0b0cbd55c3 | ||
| 
						 | 
					e21230bbd9 | ||
| 
						 | 
					84cf1bb964 | ||
| 
						 | 
					b64839cb73 | ||
| 
						 | 
					608ec3a81d | ||
| 
						 | 
					9d21fc8f9e | ||
| 
						 | 
					81566bc648 | ||
| 
						 | 
					b22aa941f5 | ||
| 
						 | 
					b12cbe4124 | ||
| 
						 | 
					086d31eab5 | ||
| 
						 | 
					8e29a97404 | ||
| 
						 | 
					ebf6695d25 | ||
| 
						 | 
					c4dabccdae | ||
| 
						 | 
					0a15892dde | ||
| 
						 | 
					005ebfba31 | ||
| 
						 | 
					eb2755c8ae | ||
| 
						 | 
					2b38d6f81e | ||
| 
						 | 
					ed9046d3e1 | 
@@ -3,11 +3,28 @@ require('@rushstack/eslint-patch/modern-module-resolution');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
  root: true,
 | 
					  root: true,
 | 
				
			||||||
  extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/vue3-recommended', '@vue/eslint-config-typescript/recommended'],
 | 
					  extends: [
 | 
				
			||||||
 | 
					    'plugin:vue/vue3-essential',
 | 
				
			||||||
 | 
					    'eslint:recommended',
 | 
				
			||||||
 | 
					    'plugin:vue/vue3-recommended',
 | 
				
			||||||
 | 
					    'plugin:vue/vue3-recommended',
 | 
				
			||||||
 | 
					    '@vue/eslint-config-typescript/recommended',
 | 
				
			||||||
 | 
					    '@vue/eslint-config-prettier',
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  env: {
 | 
					  env: {
 | 
				
			||||||
    'vue/setup-compiler-macros': true,
 | 
					    'vue/setup-compiler-macros': true,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  rules: {
 | 
					  rules: {
 | 
				
			||||||
    'vue/multi-word-component-names': ['off'],
 | 
					    'vue/multi-word-component-names': ['off'],
 | 
				
			||||||
 | 
					    'prettier/prettier': [
 | 
				
			||||||
 | 
					      'error',
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        singleQuote: true,
 | 
				
			||||||
 | 
					        semi: true,
 | 
				
			||||||
 | 
					        tabWidth: 2,
 | 
				
			||||||
 | 
					        trailingComma: 'all',
 | 
				
			||||||
 | 
					        printWidth: 120,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<!-- Thank you for contributing! -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Description
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Additional context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- e.g. is there anything you'd like reviewers to focus on? -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] Bug fix
 | 
				
			||||||
 | 
					- [ ] New Feature
 | 
				
			||||||
 | 
					- [ ] Documentation update
 | 
				
			||||||
 | 
					- [ ] Other
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Before submitting the PR, please make sure you do the following
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [ ] Submit the PR against the `dev` branch.
 | 
				
			||||||
 | 
					- [ ] Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
 | 
				
			||||||
 | 
					- [ ] Provide a description in this PR that addresses **what** the PR is solving, or reference the issue that it solves (e.g. `fixes #123`).
 | 
				
			||||||
 | 
					- [ ] Ideally, include relevant tests that fail without this PR but pass with it.
 | 
				
			||||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
name: ci
 | 
					name: ci
 | 
				
			||||||
 | 
					
 | 
				
			||||||
on: push
 | 
					on: [push, pull_request]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  ci:
 | 
					  ci:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,3 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "recommendations": ["johnsoncodehk.volar", "johnsoncodehk.vscode-typescript-vue-plugin"]
 | 
					  "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin", "dbaeumer.vscode-eslint"]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										135
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										135
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -2,6 +2,141 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 | 
					All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [2.5.0](https://github.com/CorentinTh/it-tools/compare/v2.4.2...v2.5.0) (2022-06-01)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **new-tool:** math evaluator ([433ba2a](https://github.com/CorentinTh/it-tools/commit/433ba2a3e5419eed0c96304b37693082224a1c73))
 | 
				
			||||||
 | 
					* **tools:** new badge for recently created tools ([11720e6](https://github.com/CorentinTh/it-tools/commit/11720e6cdefc1da4bdd638415813b609840f8462))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **config:** updated env values loading ([2f61c74](https://github.com/CorentinTh/it-tools/commit/2f61c745f57962cf3bb9e2c1db4a3176df042808))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Refactors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* removed unused import ([8fb0e6a](https://github.com/CorentinTh/it-tools/commit/8fb0e6af9c3be708d3f1777a1661e1b38f197a3f))
 | 
				
			||||||
 | 
					* renammed Tool.ts to tool.ts ([ac89490](https://github.com/CorentinTh/it-tools/commit/ac89490794ee3c1c033859ffea31a962a13cc96d))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### [2.4.2](https://github.com/CorentinTh/it-tools/compare/v2.4.1...v2.4.2) (2022-06-01)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Refactors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **config:** added config management with figue ([6becdbb](https://github.com/CorentinTh/it-tools/commit/6becdbb42329e1bdecf158707e37ba9f13ba1d2c))
 | 
				
			||||||
 | 
					* **imports:** removed useless defineProps import ([5ce1262](https://github.com/CorentinTh/it-tools/commit/5ce1262fb44864b829dac09d5c0b9b68d522ceb7))
 | 
				
			||||||
 | 
					* set coerent head title for home page ([a46d125](https://github.com/CorentinTh/it-tools/commit/a46d125c19902c2f41f37c62c07bb7b548d9f6f0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### [2.4.1](https://github.com/CorentinTh/it-tools/compare/v2.4.0...v2.4.1) (2022-05-15)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **seo:** wrong url in share metas ([a88e4a9](https://github.com/CorentinTh/it-tools/commit/a88e4a9289e7d8cc80190f60f2fe08fe2ba08ee6))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Refactors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **json-viewer:** add clear button ([048bc4a](https://github.com/CorentinTh/it-tools/commit/048bc4ae943509dea2946764efaa69f845b6c478))
 | 
				
			||||||
 | 
					* **seo:** changed title string ([d4ea393](https://github.com/CorentinTh/it-tools/commit/d4ea393c1df87ae958a06ed66a11e36b081282d4))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [2.4.0](https://github.com/CorentinTh/it-tools/compare/v2.3.2...v2.4.0) (2022-05-14)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* catch throw on validation ([a60f64f](https://github.com/CorentinTh/it-tools/commit/a60f64f74417f811204121f97c16cdb4754afc3b))
 | 
				
			||||||
 | 
					* **hash-text:** compute all hashes at the same time ([#242](https://github.com/CorentinTh/it-tools/issues/242)) ([e9cc499](https://github.com/CorentinTh/it-tools/commit/e9cc499ed87ba926086323223c7eca4f6658b3f0))
 | 
				
			||||||
 | 
					* **new-tool:**  json viewer ([d356b14](https://github.com/CorentinTh/it-tools/commit/d356b1488fc640a4f5b65d62e0f2f368f5941996))
 | 
				
			||||||
 | 
					* **seo:** added cannonical meta ([34bc6a5](https://github.com/CorentinTh/it-tools/commit/34bc6a57a7bab98ff2a630d02034c342084e0af9))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **lint:** missing new lines ([3cfc5f8](https://github.com/CorentinTh/it-tools/commit/3cfc5f8bc27b66e6fbb6054f3c909818083ebc37))
 | 
				
			||||||
 | 
					* update recommended extension ids ([#244](https://github.com/CorentinTh/it-tools/issues/244)) ([1d7032d](https://github.com/CorentinTh/it-tools/commit/1d7032d0268220f594de6d837a303fc1e63cbd9f))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Documentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* added producthunt banners ([4c4da16](https://github.com/CorentinTh/it-tools/commit/4c4da16970e1dbb13705d8b6c020cd40cd2b5e0d))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Refactors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **base-layout:** renammed one letter variable ([383d975](https://github.com/CorentinTh/it-tools/commit/383d97569580c4f31448c07cb97e3778bc97a8af))
 | 
				
			||||||
 | 
					* **date-converter:** mutualised and dry-ed code ([d2c767f](https://github.com/CorentinTh/it-tools/commit/d2c767f0922e9b93172c3167226ad3db5499b9f6))
 | 
				
			||||||
 | 
					* **seo:** changed title string ([c3b6132](https://github.com/CorentinTh/it-tools/commit/c3b6132c261bd5952bafb1ff1e576eb13d2d0a7d))
 | 
				
			||||||
 | 
					* updated description ([b89db3c](https://github.com/CorentinTh/it-tools/commit/b89db3c8d0de601fecbd2f9f79492dff1b461bd8))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### [2.3.2](https://github.com/CorentinTh/it-tools/compare/v2.3.1...v2.3.2) (2022-05-09)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **base-converter:** responsive input ([0b0cbd5](https://github.com/CorentinTh/it-tools/commit/0b0cbd55c3809ded2eedfa0b2238bc950b01516a))
 | 
				
			||||||
 | 
					* **base64-converter:** async onUpload callback ([84cf1bb](https://github.com/CorentinTh/it-tools/commit/84cf1bb9645c5ae31579098df59471f7d99f6f0c))
 | 
				
			||||||
 | 
					* **typo:** misspelings ([9755e51](https://github.com/CorentinTh/it-tools/commit/9755e51fe216e5e25c56417152e70cb5bce26b11))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Refactors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **responsive:** row layout for multicards on big screens ([e21230b](https://github.com/CorentinTh/it-tools/commit/e21230bbd9550ba3315607b021a60a4f9f9e1b61))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### [2.3.1](https://github.com/CorentinTh/it-tools/compare/v2.3.0...v2.3.1) (2022-04-24)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Refactors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* changed twitter account handler ([608ec3a](https://github.com/CorentinTh/it-tools/commit/608ec3a81db6583c8a2bf126b3868afd043c6981))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [2.3.0](https://github.com/CorentinTh/it-tools/compare/v2.2.0...v2.3.0) (2022-04-22)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **new-tool:** html entities escape/unescape ([8e29a97](https://github.com/CorentinTh/it-tools/commit/8e29a97404ea0aa9b9b576656358c8c276b6f992))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **head:** added titles for non-tool pages ([0a15892](https://github.com/CorentinTh/it-tools/commit/0a15892dde9852ff158a8fcb72d0ad6bae8bad02))
 | 
				
			||||||
 | 
					* **sider:** default collapsed value ([b22aa94](https://github.com/CorentinTh/it-tools/commit/b22aa941f52009118d4d3cc98277cc4c402a4c77))
 | 
				
			||||||
 | 
					* **sider:** missing href for link in footer ([c4dabcc](https://github.com/CorentinTh/it-tools/commit/c4dabccdaeac9d03163ac2588599b000e4e74562))
 | 
				
			||||||
 | 
					* **style:** hard width for group labels ([ebf6695](https://github.com/CorentinTh/it-tools/commit/ebf6695d2533db6f37b24dc7d338f422c551c8cb))
 | 
				
			||||||
 | 
					* **url-parser:** cleaned weird margins on dark mode ([005ebfb](https://github.com/CorentinTh/it-tools/commit/005ebfba318ece1a9c04aefb737baed5d7aafb91))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Refactors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **lint:** linter auto fix ([086d31e](https://github.com/CorentinTh/it-tools/commit/086d31eab5b3b1a927803eab5e650585f61abe19))
 | 
				
			||||||
 | 
					* removed useless ref and value ([b12cbe4](https://github.com/CorentinTh/it-tools/commit/b12cbe412407389186a58e4ceaa94f5b441c11ea))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### [2.2.1](https://github.com/CorentinTh/it-tools/compare/v2.2.0...v2.2.1) (2022-04-21)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **head:** added titles for non-tool pages ([0a15892](https://github.com/CorentinTh/it-tools/commit/0a15892dde9852ff158a8fcb72d0ad6bae8bad02))
 | 
				
			||||||
 | 
					* **sider:** missing href for link in footer ([c4dabcc](https://github.com/CorentinTh/it-tools/commit/c4dabccdaeac9d03163ac2588599b000e4e74562))
 | 
				
			||||||
 | 
					* **style:** hard width for group labels ([ebf6695](https://github.com/CorentinTh/it-tools/commit/ebf6695d2533db6f37b24dc7d338f422c551c8cb))
 | 
				
			||||||
 | 
					* **url-parser:** cleaned weird margins on dark mode ([005ebfb](https://github.com/CorentinTh/it-tools/commit/005ebfba318ece1a9c04aefb737baed5d7aafb91))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [2.2.0](https://github.com/CorentinTh/it-tools/compare/v2.1.0...v2.2.0) (2022-04-18)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **new-tool:** url parser ([2b38d6f](https://github.com/CorentinTh/it-tools/commit/2b38d6f81e34845f896b858513e35209cba29f98))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Bug Fixes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* **sider-footer:** fixed commit sha url ([ed9046d](https://github.com/CorentinTh/it-tools/commit/ed9046d3e1f5a7dc01c722ed139a2ae477a2d48f))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## [2.1.0](https://github.com/CorentinTh/it-tools/compare/v2.0.2...v2.1.0) (2022-04-18)
 | 
					## [2.1.0](https://github.com/CorentinTh/it-tools/compare/v2.0.2...v2.1.0) (2022-04-18)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,6 +68,12 @@ Coded with ❤️ by [Corentin Thomasset](//corentin-thomasset.fr).
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
This project is continuously deployed using [vercel.com](https://vercel.com).
 | 
					This project is continuously deployed using [vercel.com](https://vercel.com).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<a href="https://www.producthunt.com/posts/it-tools?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-it-tools" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=345793&theme=light" alt="IT Tools - Collection of handy online tools for devs, with great UX | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
 | 
				
			||||||
 | 
					<a href="https://www.producthunt.com/posts/it-tools?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-it-tools" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=345793&theme=light&period=daily" alt="IT Tools - Collection of handy online tools for devs, with great UX | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## License
 | 
					## License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This project is under the [MIT license](LICENSE).
 | 
					This project is under the [MIT license](LICENSE).
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								index.html
									
									
									
									
									
								
							@@ -4,12 +4,12 @@
 | 
				
			|||||||
    <meta charset="UTF-8" />
 | 
					    <meta charset="UTF-8" />
 | 
				
			||||||
    <link rel="icon" href="/favicon.ico" />
 | 
					    <link rel="icon" href="/favicon.ico" />
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
				
			||||||
    <meta name="description" content="Aggregated set of useful tools that every developer may need once in a while." />
 | 
					    <title>IT Tools - Handy online tools for developers</title>
 | 
				
			||||||
    <title>IT Tools</title>
 | 
					    <meta itemprop="name" content="IT Tools - Handy online tools for developers" />
 | 
				
			||||||
    <meta itemprop="name" content="IT-Tools" />
 | 
					    <meta name="description" content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT." />
 | 
				
			||||||
    <meta name="description" content="Aggregated set of useful tools that every developer may need once in a while." />
 | 
					    <meta itemprop="description" content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT." />
 | 
				
			||||||
    <meta itemprop="description" content="Aggregated set of useful tools that every developer may need once in a while." />
 | 
					 | 
				
			||||||
    <link rel="author" href="/humans.txt" />
 | 
					    <link rel="author" href="/humans.txt" />
 | 
				
			||||||
 | 
					    <link rel=canonical href="https://it-tools.tech">
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
 | 
					    <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
 | 
				
			||||||
    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
 | 
					    <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
 | 
				
			||||||
@@ -18,17 +18,17 @@
 | 
				
			|||||||
    <meta name="msapplication-TileColor" content="#da532c" />
 | 
					    <meta name="msapplication-TileColor" content="#da532c" />
 | 
				
			||||||
    <meta name="theme-color" content="#ffffff" />
 | 
					    <meta name="theme-color" content="#ffffff" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <meta property="og:url" content="https://dev.it-tools.tech/" />
 | 
					    <meta property="og:url" content="https://it-tools.tech/" />
 | 
				
			||||||
    <meta property="og:type" content="website" />
 | 
					    <meta property="og:type" content="website" />
 | 
				
			||||||
    <meta property="og:title" content="IT-Tools" />
 | 
					    <meta property="og:title" content="IT Tools - Handy online tools for developers" />
 | 
				
			||||||
    <meta property="og:description" content="Aggregated set of useful tools that every developer may need once in a while." />
 | 
					    <meta property="og:description" content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT." />
 | 
				
			||||||
    <meta property="og:image" content="/banner.png" />
 | 
					    <meta property="og:image" content="/banner.png" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <meta name="twitter:card" content="summary_large_image" />
 | 
					    <meta name="twitter:card" content="summary_large_image" />
 | 
				
			||||||
    <meta property="twitter:domain" content="dev.it-tools.tech" />
 | 
					    <meta property="twitter:domain" content="it-tools.tech" />
 | 
				
			||||||
    <meta property="twitter:url" content="https://dev.it-tools.tech/" />
 | 
					    <meta property="twitter:url" content="https://it-tools.tech/" />
 | 
				
			||||||
    <meta name="twitter:title" content="IT-Tools" />
 | 
					    <meta name="twitter:title" content="IT Tools - Handy online tools for developers" />
 | 
				
			||||||
    <meta name="twitter:description" content="Aggregated set of useful tools that every developer may need once in a while." />
 | 
					    <meta name="twitter:description" content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT." />
 | 
				
			||||||
    <meta name="twitter:image" content="/banner.png" />
 | 
					    <meta name="twitter:image" content="/banner.png" />
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										521
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										521
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,12 +1,12 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "it-tools",
 | 
					  "name": "it-tools",
 | 
				
			||||||
  "version": "2.1.0",
 | 
					  "version": "2.5.0",
 | 
				
			||||||
  "lockfileVersion": 2,
 | 
					  "lockfileVersion": 2,
 | 
				
			||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "it-tools",
 | 
					      "name": "it-tools",
 | 
				
			||||||
      "version": "2.1.0",
 | 
					      "version": "2.5.0",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@it-tools/bip39": "^0.0.4",
 | 
					        "@it-tools/bip39": "^0.0.4",
 | 
				
			||||||
        "@vicons/material": "^0.12.0",
 | 
					        "@vicons/material": "^0.12.0",
 | 
				
			||||||
@@ -20,7 +20,10 @@
 | 
				
			|||||||
        "cronstrue": "^2.2.0",
 | 
					        "cronstrue": "^2.2.0",
 | 
				
			||||||
        "crypto-js": "^4.1.1",
 | 
					        "crypto-js": "^4.1.1",
 | 
				
			||||||
        "date-fns": "^2.28.0",
 | 
					        "date-fns": "^2.28.0",
 | 
				
			||||||
 | 
					        "figue": "^1.2.0",
 | 
				
			||||||
 | 
					        "highlight.js": "^11.5.1",
 | 
				
			||||||
        "lodash": "^4.17.21",
 | 
					        "lodash": "^4.17.21",
 | 
				
			||||||
 | 
					        "mathjs": "^10.6.0",
 | 
				
			||||||
        "naive-ui": "^2.28.0",
 | 
					        "naive-ui": "^2.28.0",
 | 
				
			||||||
        "pinia": "^2.0.11",
 | 
					        "pinia": "^2.0.11",
 | 
				
			||||||
        "plausible-tracker": "^0.3.5",
 | 
					        "plausible-tracker": "^0.3.5",
 | 
				
			||||||
@@ -41,6 +44,7 @@
 | 
				
			|||||||
        "@types/uuid": "^8.3.4",
 | 
					        "@types/uuid": "^8.3.4",
 | 
				
			||||||
        "@vitejs/plugin-vue": "^2.2.2",
 | 
					        "@vitejs/plugin-vue": "^2.2.2",
 | 
				
			||||||
        "@vitejs/plugin-vue-jsx": "^1.3.7",
 | 
					        "@vitejs/plugin-vue-jsx": "^1.3.7",
 | 
				
			||||||
 | 
					        "@vue/eslint-config-prettier": "^7.0.0",
 | 
				
			||||||
        "@vue/eslint-config-typescript": "^10.0.0",
 | 
					        "@vue/eslint-config-typescript": "^10.0.0",
 | 
				
			||||||
        "@vue/test-utils": "^2.0.0-rc.18",
 | 
					        "@vue/test-utils": "^2.0.0-rc.18",
 | 
				
			||||||
        "@vue/tsconfig": "^0.1.3",
 | 
					        "@vue/tsconfig": "^0.1.3",
 | 
				
			||||||
@@ -49,7 +53,7 @@
 | 
				
			|||||||
        "eslint-plugin-vue": "^8.2.0",
 | 
					        "eslint-plugin-vue": "^8.2.0",
 | 
				
			||||||
        "jsdom": "^19.0.0",
 | 
					        "jsdom": "^19.0.0",
 | 
				
			||||||
        "less": "^4.1.2",
 | 
					        "less": "^4.1.2",
 | 
				
			||||||
        "prettier": "^2.5.1",
 | 
					        "prettier": "^2.6.2",
 | 
				
			||||||
        "standard-version": "^9.3.2",
 | 
					        "standard-version": "^9.3.2",
 | 
				
			||||||
        "start-server-and-test": "^1.14.0",
 | 
					        "start-server-and-test": "^1.14.0",
 | 
				
			||||||
        "typescript": "~4.5.5",
 | 
					        "typescript": "~4.5.5",
 | 
				
			||||||
@@ -57,7 +61,7 @@
 | 
				
			|||||||
        "vite-plugin-md": "^0.12.4",
 | 
					        "vite-plugin-md": "^0.12.4",
 | 
				
			||||||
        "vite-plugin-pwa": "^0.11.13",
 | 
					        "vite-plugin-pwa": "^0.11.13",
 | 
				
			||||||
        "vite-svg-loader": "^3.2.0",
 | 
					        "vite-svg-loader": "^3.2.0",
 | 
				
			||||||
        "vitest": "^0.5.0",
 | 
					        "vitest": "^0.13.1",
 | 
				
			||||||
        "vue-tsc": "^0.31.4"
 | 
					        "vue-tsc": "^0.31.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -1640,7 +1644,6 @@
 | 
				
			|||||||
      "version": "7.17.9",
 | 
					      "version": "7.17.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
 | 
					      "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "regenerator-runtime": "^0.13.4"
 | 
					        "regenerator-runtime": "^0.13.4"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -2121,9 +2124,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/chai": {
 | 
					    "node_modules/@types/chai": {
 | 
				
			||||||
      "version": "4.3.0",
 | 
					      "version": "4.3.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==",
 | 
					      "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@types/chai-subset": {
 | 
					    "node_modules/@types/chai-subset": {
 | 
				
			||||||
@@ -2722,6 +2725,20 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
 | 
					      "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@vue/eslint-config-prettier": {
 | 
				
			||||||
 | 
					      "version": "7.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-/CTc6ML3Wta1tCe1gUeO0EYnVXfo3nJXsIhZ8WJr3sov+cGASr6yuiibJTL6lmIBm7GobopToOuB3B6AWyV0Iw==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "eslint-config-prettier": "^8.3.0",
 | 
				
			||||||
 | 
					        "eslint-plugin-prettier": "^4.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "eslint": ">= 7.28.0",
 | 
				
			||||||
 | 
					        "prettier": ">= 2.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@vue/eslint-config-typescript": {
 | 
					    "node_modules/@vue/eslint-config-typescript": {
 | 
				
			||||||
      "version": "10.0.0",
 | 
					      "version": "10.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz",
 | 
				
			||||||
@@ -3086,6 +3103,12 @@
 | 
				
			|||||||
        "node": "*"
 | 
					        "node": "*"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/async": {
 | 
				
			||||||
 | 
					      "version": "3.2.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/async-validator": {
 | 
					    "node_modules/async-validator": {
 | 
				
			||||||
      "version": "4.0.7",
 | 
					      "version": "4.0.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz",
 | 
				
			||||||
@@ -3457,7 +3480,7 @@
 | 
				
			|||||||
    "node_modules/check-error": {
 | 
					    "node_modules/check-error": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
 | 
					      "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": "*"
 | 
					        "node": "*"
 | 
				
			||||||
@@ -3535,6 +3558,18 @@
 | 
				
			|||||||
        "dot-prop": "^5.1.0"
 | 
					        "dot-prop": "^5.1.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/complex.js": {
 | 
				
			||||||
 | 
					      "version": "2.1.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": "*"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "patreon",
 | 
				
			||||||
 | 
					        "url": "https://www.patreon.com/infusion"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/concat-map": {
 | 
					    "node_modules/concat-map": {
 | 
				
			||||||
      "version": "0.0.1",
 | 
					      "version": "0.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
				
			||||||
@@ -4141,8 +4176,7 @@
 | 
				
			|||||||
    "node_modules/decimal.js": {
 | 
					    "node_modules/decimal.js": {
 | 
				
			||||||
      "version": "10.3.1",
 | 
					      "version": "10.3.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
 | 
					      "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/deep-eql": {
 | 
					    "node_modules/deep-eql": {
 | 
				
			||||||
      "version": "3.0.1",
 | 
					      "version": "3.0.1",
 | 
				
			||||||
@@ -4444,12 +4478,12 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/ejs": {
 | 
					    "node_modules/ejs": {
 | 
				
			||||||
      "version": "3.1.6",
 | 
					      "version": "3.1.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.7.tgz",
 | 
				
			||||||
      "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
 | 
					      "integrity": "sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "jake": "^10.6.1"
 | 
					        "jake": "^10.8.5"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "bin": {
 | 
					      "bin": {
 | 
				
			||||||
        "ejs": "bin/cli.js"
 | 
					        "ejs": "bin/cli.js"
 | 
				
			||||||
@@ -4933,6 +4967,11 @@
 | 
				
			|||||||
        "node": ">=6"
 | 
					        "node": ">=6"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/escape-latex": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/escape-string-regexp": {
 | 
					    "node_modules/escape-string-regexp": {
 | 
				
			||||||
      "version": "1.0.5",
 | 
					      "version": "1.0.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
 | 
				
			||||||
@@ -5086,6 +5125,39 @@
 | 
				
			|||||||
        "url": "https://opencollective.com/eslint"
 | 
					        "url": "https://opencollective.com/eslint"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/eslint-config-prettier": {
 | 
				
			||||||
 | 
					      "version": "8.5.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "bin": {
 | 
				
			||||||
 | 
					        "eslint-config-prettier": "bin/cli.js"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "eslint": ">=7.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/eslint-plugin-prettier": {
 | 
				
			||||||
 | 
					      "version": "4.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "prettier-linter-helpers": "^1.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=6.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "eslint": ">=7.28.0",
 | 
				
			||||||
 | 
					        "prettier": ">=2.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependenciesMeta": {
 | 
				
			||||||
 | 
					        "eslint-config-prettier": {
 | 
				
			||||||
 | 
					          "optional": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/eslint-plugin-vue": {
 | 
					    "node_modules/eslint-plugin-vue": {
 | 
				
			||||||
      "version": "8.5.0",
 | 
					      "version": "8.5.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.5.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.5.0.tgz",
 | 
				
			||||||
@@ -5411,6 +5483,12 @@
 | 
				
			|||||||
      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 | 
					      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/fast-diff": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/fast-glob": {
 | 
					    "node_modules/fast-glob": {
 | 
				
			||||||
      "version": "3.2.11",
 | 
					      "version": "3.2.11",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
 | 
				
			||||||
@@ -5460,6 +5538,17 @@
 | 
				
			|||||||
        "reusify": "^1.0.4"
 | 
					        "reusify": "^1.0.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/figue": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/figue/-/figue-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-CXKr12kiNWjKtUK3X+YHeXKepn80s9Rg6pgZXoLQYEybgwaGJ9uGW4DrBrVK30ZWZf1mcvTbXF56AcovG7gLVw==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "lodash": "^4.17.21"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "url": "https://github.com/sponsors/CorentinTh"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/figures": {
 | 
					    "node_modules/figures": {
 | 
				
			||||||
      "version": "3.2.0",
 | 
					      "version": "3.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
 | 
				
			||||||
@@ -5488,12 +5577,33 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/filelist": {
 | 
					    "node_modules/filelist": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==",
 | 
					      "integrity": "sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "minimatch": "^3.0.4"
 | 
					        "minimatch": "^5.0.1"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/filelist/node_modules/brace-expansion": {
 | 
				
			||||||
 | 
					      "version": "2.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "balanced-match": "^1.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/filelist/node_modules/minimatch": {
 | 
				
			||||||
 | 
					      "version": "5.0.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "brace-expansion": "^2.0.1"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=10"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/fill-range": {
 | 
					    "node_modules/fill-range": {
 | 
				
			||||||
@@ -5576,6 +5686,18 @@
 | 
				
			|||||||
        "node": ">=8.0.0"
 | 
					        "node": ">=8.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/fraction.js": {
 | 
				
			||||||
 | 
					      "version": "4.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": "*"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "patreon",
 | 
				
			||||||
 | 
					        "url": "https://www.patreon.com/infusion"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/from": {
 | 
					    "node_modules/from": {
 | 
				
			||||||
      "version": "0.1.7",
 | 
					      "version": "0.1.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
 | 
				
			||||||
@@ -5670,7 +5792,7 @@
 | 
				
			|||||||
    "node_modules/get-func-name": {
 | 
					    "node_modules/get-func-name": {
 | 
				
			||||||
      "version": "2.0.0",
 | 
					      "version": "2.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
 | 
					      "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": "*"
 | 
					        "node": "*"
 | 
				
			||||||
@@ -6650,12 +6772,12 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/jake": {
 | 
					    "node_modules/jake": {
 | 
				
			||||||
      "version": "10.8.4",
 | 
					      "version": "10.8.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-MtWeTkl1qGsWUtbl/Jsca/8xSoK3x0UmS82sNbjqxxG/de/M/3b1DntdjHgPMC50enlTNwXOCRqPXLLt5cCfZA==",
 | 
					      "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "async": "0.9.x",
 | 
					        "async": "^3.2.3",
 | 
				
			||||||
        "chalk": "^4.0.2",
 | 
					        "chalk": "^4.0.2",
 | 
				
			||||||
        "filelist": "^1.0.1",
 | 
					        "filelist": "^1.0.1",
 | 
				
			||||||
        "minimatch": "^3.0.4"
 | 
					        "minimatch": "^3.0.4"
 | 
				
			||||||
@@ -6682,12 +6804,6 @@
 | 
				
			|||||||
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
 | 
					        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/jake/node_modules/async": {
 | 
					 | 
				
			||||||
      "version": "0.9.2",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
 | 
					 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "node_modules/jake/node_modules/chalk": {
 | 
					    "node_modules/jake/node_modules/chalk": {
 | 
				
			||||||
      "version": "4.1.2",
 | 
					      "version": "4.1.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 | 
				
			||||||
@@ -6725,6 +6841,11 @@
 | 
				
			|||||||
        "node": ">=8"
 | 
					        "node": ">=8"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/javascript-natural-sort": {
 | 
				
			||||||
 | 
					      "version": "0.7.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/jest-diff": {
 | 
					    "node_modules/jest-diff": {
 | 
				
			||||||
      "version": "27.5.1",
 | 
					      "version": "27.5.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
 | 
				
			||||||
@@ -7429,6 +7550,28 @@
 | 
				
			|||||||
        "url": "https://github.com/fb55/entities?sponsor=1"
 | 
					        "url": "https://github.com/fb55/entities?sponsor=1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/mathjs": {
 | 
				
			||||||
 | 
					      "version": "10.6.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-10.6.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-4oI0CSX7LtcyexTSLV8uo+llj8hB5LvVE9ApjN6rBjBplQaZ4/Gr3jh0zEla9+KaCig5wonZ9oFKD+GKXFL8hg==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@babel/runtime": "^7.17.9",
 | 
				
			||||||
 | 
					        "complex.js": "^2.1.1",
 | 
				
			||||||
 | 
					        "decimal.js": "^10.3.1",
 | 
				
			||||||
 | 
					        "escape-latex": "^1.2.0",
 | 
				
			||||||
 | 
					        "fraction.js": "^4.2.0",
 | 
				
			||||||
 | 
					        "javascript-natural-sort": "^0.7.1",
 | 
				
			||||||
 | 
					        "seedrandom": "^3.0.5",
 | 
				
			||||||
 | 
					        "tiny-emitter": "^2.1.0",
 | 
				
			||||||
 | 
					        "typed-function": "^2.1.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "bin": {
 | 
				
			||||||
 | 
					        "mathjs": "bin/cli.js"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">= 14"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/mdn-data": {
 | 
					    "node_modules/mdn-data": {
 | 
				
			||||||
      "version": "2.0.14",
 | 
					      "version": "2.0.14",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
 | 
				
			||||||
@@ -7781,9 +7924,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/nanoid": {
 | 
					    "node_modules/nanoid": {
 | 
				
			||||||
      "version": "3.3.2",
 | 
					      "version": "3.3.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==",
 | 
					      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
 | 
				
			||||||
      "bin": {
 | 
					      "bin": {
 | 
				
			||||||
        "nanoid": "bin/nanoid.cjs"
 | 
					        "nanoid": "bin/nanoid.cjs"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -8271,9 +8414,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/postcss": {
 | 
					    "node_modules/postcss": {
 | 
				
			||||||
      "version": "8.4.12",
 | 
					      "version": "8.4.14",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
 | 
				
			||||||
      "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==",
 | 
					      "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
 | 
				
			||||||
      "funding": [
 | 
					      "funding": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          "type": "opencollective",
 | 
					          "type": "opencollective",
 | 
				
			||||||
@@ -8285,7 +8428,7 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "nanoid": "^3.3.1",
 | 
					        "nanoid": "^3.3.4",
 | 
				
			||||||
        "picocolors": "^1.0.0",
 | 
					        "picocolors": "^1.0.0",
 | 
				
			||||||
        "source-map-js": "^1.0.2"
 | 
					        "source-map-js": "^1.0.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -8303,9 +8446,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/prettier": {
 | 
					    "node_modules/prettier": {
 | 
				
			||||||
      "version": "2.6.1",
 | 
					      "version": "2.6.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==",
 | 
					      "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "bin": {
 | 
					      "bin": {
 | 
				
			||||||
        "prettier": "bin-prettier.js"
 | 
					        "prettier": "bin-prettier.js"
 | 
				
			||||||
@@ -8317,6 +8460,18 @@
 | 
				
			|||||||
        "url": "https://github.com/prettier/prettier?sponsor=1"
 | 
					        "url": "https://github.com/prettier/prettier?sponsor=1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/prettier-linter-helpers": {
 | 
				
			||||||
 | 
					      "version": "1.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "fast-diff": "^1.1.2"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=6.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/pretty-bytes": {
 | 
					    "node_modules/pretty-bytes": {
 | 
				
			||||||
      "version": "5.6.0",
 | 
					      "version": "5.6.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
 | 
				
			||||||
@@ -8919,8 +9074,7 @@
 | 
				
			|||||||
    "node_modules/regenerator-runtime": {
 | 
					    "node_modules/regenerator-runtime": {
 | 
				
			||||||
      "version": "0.13.9",
 | 
					      "version": "0.13.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
 | 
					      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/regenerator-transform": {
 | 
					    "node_modules/regenerator-transform": {
 | 
				
			||||||
      "version": "0.15.0",
 | 
					      "version": "0.15.0",
 | 
				
			||||||
@@ -9187,6 +9341,11 @@
 | 
				
			|||||||
        "node": ">=4"
 | 
					        "node": ">=4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/seedrandom": {
 | 
				
			||||||
 | 
					      "version": "3.0.5",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/seemly": {
 | 
					    "node_modules/seemly": {
 | 
				
			||||||
      "version": "0.3.3",
 | 
					      "version": "0.3.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.3.tgz",
 | 
				
			||||||
@@ -9890,19 +10049,24 @@
 | 
				
			|||||||
        "readable-stream": "3"
 | 
					        "readable-stream": "3"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/tiny-emitter": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/tinypool": {
 | 
					    "node_modules/tinypool": {
 | 
				
			||||||
      "version": "0.1.2",
 | 
					      "version": "0.1.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-fvtYGXoui2RpeMILfkvGIgOVkzJEGediv8UJt7TxdAOY8pnvUkFg/fkvqTfXG9Acc9S17Cnn1S4osDc2164guA==",
 | 
					      "integrity": "sha512-2IfcQh7CP46XGWGGbdyO4pjcKqsmVqFAPcXfPxcPXmOWt9cYkTP9HcDmGgsfijYoAEc4z9qcpM/BaBz46Y9/CQ==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=14.0.0"
 | 
					        "node": ">=14.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/tinyspy": {
 | 
					    "node_modules/tinyspy": {
 | 
				
			||||||
      "version": "0.3.0",
 | 
					      "version": "0.3.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-0.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-0.3.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-c5uFHqtUp74R2DJE3/Efg0mH5xicmgziaQXMm/LvuuZn3RdpADH32aEGDRyCzObXT1DNfwDMqRQ/Drh1MlO12g==",
 | 
					      "integrity": "sha512-2+40EP4D3sFYy42UkgkFFB+kiX2Tg3URG/lVvAZFfLxgGpnWl5qQJuBw1gaLttq8UOS+2p3C0WrhJnQigLTT2Q==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=14.0.0"
 | 
					        "node": ">=14.0.0"
 | 
				
			||||||
@@ -10008,6 +10172,14 @@
 | 
				
			|||||||
        "node": ">=4"
 | 
					        "node": ">=4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/typed-function": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==",
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">= 10"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/typedarray": {
 | 
					    "node_modules/typedarray": {
 | 
				
			||||||
      "version": "0.0.6",
 | 
					      "version": "0.0.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
 | 
				
			||||||
@@ -10227,13 +10399,13 @@
 | 
				
			|||||||
      "integrity": "sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ=="
 | 
					      "integrity": "sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/vite": {
 | 
					    "node_modules/vite": {
 | 
				
			||||||
      "version": "2.9.1",
 | 
					      "version": "2.9.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ==",
 | 
					      "integrity": "sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "esbuild": "^0.14.27",
 | 
					        "esbuild": "^0.14.27",
 | 
				
			||||||
        "postcss": "^8.4.12",
 | 
					        "postcss": "^8.4.13",
 | 
				
			||||||
        "resolve": "^1.22.0",
 | 
					        "resolve": "^1.22.0",
 | 
				
			||||||
        "rollup": "^2.59.0"
 | 
					        "rollup": "^2.59.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
@@ -10316,24 +10488,25 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/vitest": {
 | 
					    "node_modules/vitest": {
 | 
				
			||||||
      "version": "0.5.9",
 | 
					      "version": "0.13.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.5.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.13.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-R8lRP9Q1yIbwr8pDf2gvw4PFe8H5YMyHhBcdyfnUh6toLfCR10jrdI/WkNxdo5I4H/9XrMX9t+SAavdJExNdKg==",
 | 
					      "integrity": "sha512-CfSBf7YFw/i8HumSUQRtZKs0aV91DC9WU8nAgIJAlawKHaFuPHQohDwOTPIFgrxySiuFYUa0Yohf9gDFfBwjxA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@types/chai": "^4.3.0",
 | 
					        "@types/chai": "^4.3.1",
 | 
				
			||||||
        "@types/chai-subset": "^1.3.3",
 | 
					        "@types/chai-subset": "^1.3.3",
 | 
				
			||||||
        "chai": "^4.3.6",
 | 
					        "chai": "^4.3.6",
 | 
				
			||||||
 | 
					        "debug": "^4.3.4",
 | 
				
			||||||
        "local-pkg": "^0.4.1",
 | 
					        "local-pkg": "^0.4.1",
 | 
				
			||||||
        "tinypool": "^0.1.2",
 | 
					        "tinypool": "^0.1.3",
 | 
				
			||||||
        "tinyspy": "^0.3.0",
 | 
					        "tinyspy": "^0.3.2",
 | 
				
			||||||
        "vite": "^2.7.10"
 | 
					        "vite": "^2.9.9"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "bin": {
 | 
					      "bin": {
 | 
				
			||||||
        "vitest": "vitest.mjs"
 | 
					        "vitest": "vitest.mjs"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "engines": {
 | 
					      "engines": {
 | 
				
			||||||
        "node": ">=14.14.0"
 | 
					        "node": ">=v14.16.0"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "funding": {
 | 
					      "funding": {
 | 
				
			||||||
        "url": "https://github.com/sponsors/antfu"
 | 
					        "url": "https://github.com/sponsors/antfu"
 | 
				
			||||||
@@ -12325,7 +12498,6 @@
 | 
				
			|||||||
      "version": "7.17.9",
 | 
					      "version": "7.17.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
 | 
					      "integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
 | 
				
			||||||
      "dev": true,
 | 
					 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "regenerator-runtime": "^0.13.4"
 | 
					        "regenerator-runtime": "^0.13.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -12710,9 +12882,9 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@types/chai": {
 | 
					    "@types/chai": {
 | 
				
			||||||
      "version": "4.3.0",
 | 
					      "version": "4.3.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==",
 | 
					      "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "@types/chai-subset": {
 | 
					    "@types/chai-subset": {
 | 
				
			||||||
@@ -13207,6 +13379,16 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
 | 
					      "integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "@vue/eslint-config-prettier": {
 | 
				
			||||||
 | 
					      "version": "7.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-7.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-/CTc6ML3Wta1tCe1gUeO0EYnVXfo3nJXsIhZ8WJr3sov+cGASr6yuiibJTL6lmIBm7GobopToOuB3B6AWyV0Iw==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "eslint-config-prettier": "^8.3.0",
 | 
				
			||||||
 | 
					        "eslint-plugin-prettier": "^4.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "@vue/eslint-config-typescript": {
 | 
					    "@vue/eslint-config-typescript": {
 | 
				
			||||||
      "version": "10.0.0",
 | 
					      "version": "10.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-10.0.0.tgz",
 | 
				
			||||||
@@ -13465,6 +13647,12 @@
 | 
				
			|||||||
      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
 | 
					      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "async": {
 | 
				
			||||||
 | 
					      "version": "3.2.3",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "async-validator": {
 | 
					    "async-validator": {
 | 
				
			||||||
      "version": "4.0.7",
 | 
					      "version": "4.0.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz",
 | 
				
			||||||
@@ -13755,7 +13943,7 @@
 | 
				
			|||||||
    "check-error": {
 | 
					    "check-error": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
 | 
				
			||||||
      "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
 | 
					      "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "check-more-types": {
 | 
					    "check-more-types": {
 | 
				
			||||||
@@ -13818,6 +14006,11 @@
 | 
				
			|||||||
        "dot-prop": "^5.1.0"
 | 
					        "dot-prop": "^5.1.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "complex.js": {
 | 
				
			||||||
 | 
					      "version": "2.1.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "concat-map": {
 | 
					    "concat-map": {
 | 
				
			||||||
      "version": "0.0.1",
 | 
					      "version": "0.0.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 | 
				
			||||||
@@ -14301,8 +14494,7 @@
 | 
				
			|||||||
    "decimal.js": {
 | 
					    "decimal.js": {
 | 
				
			||||||
      "version": "10.3.1",
 | 
					      "version": "10.3.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==",
 | 
					      "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "deep-eql": {
 | 
					    "deep-eql": {
 | 
				
			||||||
      "version": "3.0.1",
 | 
					      "version": "3.0.1",
 | 
				
			||||||
@@ -14529,12 +14721,12 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "ejs": {
 | 
					    "ejs": {
 | 
				
			||||||
      "version": "3.1.6",
 | 
					      "version": "3.1.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.7.tgz",
 | 
				
			||||||
      "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
 | 
					      "integrity": "sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "jake": "^10.6.1"
 | 
					        "jake": "^10.8.5"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "electron-to-chromium": {
 | 
					    "electron-to-chromium": {
 | 
				
			||||||
@@ -14801,6 +14993,11 @@
 | 
				
			|||||||
      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
 | 
					      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "escape-latex": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "escape-string-regexp": {
 | 
					    "escape-string-regexp": {
 | 
				
			||||||
      "version": "1.0.5",
 | 
					      "version": "1.0.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
 | 
				
			||||||
@@ -14990,6 +15187,22 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "eslint-config-prettier": {
 | 
				
			||||||
 | 
					      "version": "8.5.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {}
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "eslint-plugin-prettier": {
 | 
				
			||||||
 | 
					      "version": "4.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "prettier-linter-helpers": "^1.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "eslint-plugin-vue": {
 | 
					    "eslint-plugin-vue": {
 | 
				
			||||||
      "version": "8.5.0",
 | 
					      "version": "8.5.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.5.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-8.5.0.tgz",
 | 
				
			||||||
@@ -15149,6 +15362,12 @@
 | 
				
			|||||||
      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 | 
					      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "fast-diff": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "fast-glob": {
 | 
					    "fast-glob": {
 | 
				
			||||||
      "version": "3.2.11",
 | 
					      "version": "3.2.11",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
 | 
				
			||||||
@@ -15194,6 +15413,14 @@
 | 
				
			|||||||
        "reusify": "^1.0.4"
 | 
					        "reusify": "^1.0.4"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "figue": {
 | 
				
			||||||
 | 
					      "version": "1.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/figue/-/figue-1.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-CXKr12kiNWjKtUK3X+YHeXKepn80s9Rg6pgZXoLQYEybgwaGJ9uGW4DrBrVK30ZWZf1mcvTbXF56AcovG7gLVw==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "lodash": "^4.17.21"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "figures": {
 | 
					    "figures": {
 | 
				
			||||||
      "version": "3.2.0",
 | 
					      "version": "3.2.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
 | 
				
			||||||
@@ -15213,12 +15440,32 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "filelist": {
 | 
					    "filelist": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==",
 | 
					      "integrity": "sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "minimatch": "^3.0.4"
 | 
					        "minimatch": "^5.0.1"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "brace-expansion": {
 | 
				
			||||||
 | 
					          "version": "2.0.1",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
 | 
				
			||||||
 | 
					          "dev": true,
 | 
				
			||||||
 | 
					          "requires": {
 | 
				
			||||||
 | 
					            "balanced-match": "^1.0.0"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "minimatch": {
 | 
				
			||||||
 | 
					          "version": "5.0.1",
 | 
				
			||||||
 | 
					          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
 | 
				
			||||||
 | 
					          "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
 | 
				
			||||||
 | 
					          "dev": true,
 | 
				
			||||||
 | 
					          "requires": {
 | 
				
			||||||
 | 
					            "brace-expansion": "^2.0.1"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "fill-range": {
 | 
					    "fill-range": {
 | 
				
			||||||
@@ -15272,6 +15519,11 @@
 | 
				
			|||||||
        "signal-exit": "^3.0.2"
 | 
					        "signal-exit": "^3.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "fraction.js": {
 | 
				
			||||||
 | 
					      "version": "4.2.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "from": {
 | 
					    "from": {
 | 
				
			||||||
      "version": "0.1.7",
 | 
					      "version": "0.1.7",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
 | 
				
			||||||
@@ -15344,7 +15596,7 @@
 | 
				
			|||||||
    "get-func-name": {
 | 
					    "get-func-name": {
 | 
				
			||||||
      "version": "2.0.0",
 | 
					      "version": "2.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
 | 
					      "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "get-intrinsic": {
 | 
					    "get-intrinsic": {
 | 
				
			||||||
@@ -16069,12 +16321,12 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "jake": {
 | 
					    "jake": {
 | 
				
			||||||
      "version": "10.8.4",
 | 
					      "version": "10.8.5",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
 | 
				
			||||||
      "integrity": "sha512-MtWeTkl1qGsWUtbl/Jsca/8xSoK3x0UmS82sNbjqxxG/de/M/3b1DntdjHgPMC50enlTNwXOCRqPXLLt5cCfZA==",
 | 
					      "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "async": "0.9.x",
 | 
					        "async": "^3.2.3",
 | 
				
			||||||
        "chalk": "^4.0.2",
 | 
					        "chalk": "^4.0.2",
 | 
				
			||||||
        "filelist": "^1.0.1",
 | 
					        "filelist": "^1.0.1",
 | 
				
			||||||
        "minimatch": "^3.0.4"
 | 
					        "minimatch": "^3.0.4"
 | 
				
			||||||
@@ -16089,12 +16341,6 @@
 | 
				
			|||||||
            "color-convert": "^2.0.1"
 | 
					            "color-convert": "^2.0.1"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "async": {
 | 
					 | 
				
			||||||
          "version": "0.9.2",
 | 
					 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
 | 
					 | 
				
			||||||
          "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=",
 | 
					 | 
				
			||||||
          "dev": true
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "chalk": {
 | 
					        "chalk": {
 | 
				
			||||||
          "version": "4.1.2",
 | 
					          "version": "4.1.2",
 | 
				
			||||||
          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 | 
					          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 | 
				
			||||||
@@ -16122,6 +16368,11 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "javascript-natural-sort": {
 | 
				
			||||||
 | 
					      "version": "0.7.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "jest-diff": {
 | 
					    "jest-diff": {
 | 
				
			||||||
      "version": "27.5.1",
 | 
					      "version": "27.5.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
 | 
				
			||||||
@@ -16674,6 +16925,22 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "mathjs": {
 | 
				
			||||||
 | 
					      "version": "10.6.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-10.6.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-4oI0CSX7LtcyexTSLV8uo+llj8hB5LvVE9ApjN6rBjBplQaZ4/Gr3jh0zEla9+KaCig5wonZ9oFKD+GKXFL8hg==",
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "@babel/runtime": "^7.17.9",
 | 
				
			||||||
 | 
					        "complex.js": "^2.1.1",
 | 
				
			||||||
 | 
					        "decimal.js": "^10.3.1",
 | 
				
			||||||
 | 
					        "escape-latex": "^1.2.0",
 | 
				
			||||||
 | 
					        "fraction.js": "^4.2.0",
 | 
				
			||||||
 | 
					        "javascript-natural-sort": "^0.7.1",
 | 
				
			||||||
 | 
					        "seedrandom": "^3.0.5",
 | 
				
			||||||
 | 
					        "tiny-emitter": "^2.1.0",
 | 
				
			||||||
 | 
					        "typed-function": "^2.1.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "mdn-data": {
 | 
					    "mdn-data": {
 | 
				
			||||||
      "version": "2.0.14",
 | 
					      "version": "2.0.14",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
 | 
				
			||||||
@@ -16945,9 +17212,9 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "nanoid": {
 | 
					    "nanoid": {
 | 
				
			||||||
      "version": "3.3.2",
 | 
					      "version": "3.3.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
 | 
				
			||||||
      "integrity": "sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA=="
 | 
					      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "natural-compare": {
 | 
					    "natural-compare": {
 | 
				
			||||||
      "version": "1.4.0",
 | 
					      "version": "1.4.0",
 | 
				
			||||||
@@ -17295,11 +17562,11 @@
 | 
				
			|||||||
      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
 | 
					      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "postcss": {
 | 
					    "postcss": {
 | 
				
			||||||
      "version": "8.4.12",
 | 
					      "version": "8.4.14",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
 | 
				
			||||||
      "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==",
 | 
					      "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "nanoid": "^3.3.1",
 | 
					        "nanoid": "^3.3.4",
 | 
				
			||||||
        "picocolors": "^1.0.0",
 | 
					        "picocolors": "^1.0.0",
 | 
				
			||||||
        "source-map-js": "^1.0.2"
 | 
					        "source-map-js": "^1.0.2"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -17311,11 +17578,20 @@
 | 
				
			|||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "prettier": {
 | 
					    "prettier": {
 | 
				
			||||||
      "version": "2.6.1",
 | 
					      "version": "2.6.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==",
 | 
					      "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "prettier-linter-helpers": {
 | 
				
			||||||
 | 
					      "version": "1.0.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
 | 
				
			||||||
 | 
					      "dev": true,
 | 
				
			||||||
 | 
					      "requires": {
 | 
				
			||||||
 | 
					        "fast-diff": "^1.1.2"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "pretty-bytes": {
 | 
					    "pretty-bytes": {
 | 
				
			||||||
      "version": "5.6.0",
 | 
					      "version": "5.6.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
 | 
				
			||||||
@@ -17800,8 +18076,7 @@
 | 
				
			|||||||
    "regenerator-runtime": {
 | 
					    "regenerator-runtime": {
 | 
				
			||||||
      "version": "0.13.9",
 | 
					      "version": "0.13.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
 | 
					      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
 | 
				
			||||||
      "dev": true
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "regenerator-transform": {
 | 
					    "regenerator-transform": {
 | 
				
			||||||
      "version": "0.15.0",
 | 
					      "version": "0.15.0",
 | 
				
			||||||
@@ -17995,6 +18270,11 @@
 | 
				
			|||||||
        "kind-of": "^6.0.0"
 | 
					        "kind-of": "^6.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "seedrandom": {
 | 
				
			||||||
 | 
					      "version": "3.0.5",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "seemly": {
 | 
					    "seemly": {
 | 
				
			||||||
      "version": "0.3.3",
 | 
					      "version": "0.3.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.3.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/seemly/-/seemly-0.3.3.tgz",
 | 
				
			||||||
@@ -18541,16 +18821,21 @@
 | 
				
			|||||||
        "readable-stream": "3"
 | 
					        "readable-stream": "3"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "tiny-emitter": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "tinypool": {
 | 
					    "tinypool": {
 | 
				
			||||||
      "version": "0.1.2",
 | 
					      "version": "0.1.3",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.1.3.tgz",
 | 
				
			||||||
      "integrity": "sha512-fvtYGXoui2RpeMILfkvGIgOVkzJEGediv8UJt7TxdAOY8pnvUkFg/fkvqTfXG9Acc9S17Cnn1S4osDc2164guA==",
 | 
					      "integrity": "sha512-2IfcQh7CP46XGWGGbdyO4pjcKqsmVqFAPcXfPxcPXmOWt9cYkTP9HcDmGgsfijYoAEc4z9qcpM/BaBz46Y9/CQ==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "tinyspy": {
 | 
					    "tinyspy": {
 | 
				
			||||||
      "version": "0.3.0",
 | 
					      "version": "0.3.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-0.3.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-0.3.2.tgz",
 | 
				
			||||||
      "integrity": "sha512-c5uFHqtUp74R2DJE3/Efg0mH5xicmgziaQXMm/LvuuZn3RdpADH32aEGDRyCzObXT1DNfwDMqRQ/Drh1MlO12g==",
 | 
					      "integrity": "sha512-2+40EP4D3sFYy42UkgkFFB+kiX2Tg3URG/lVvAZFfLxgGpnWl5qQJuBw1gaLttq8UOS+2p3C0WrhJnQigLTT2Q==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "to-fast-properties": {
 | 
					    "to-fast-properties": {
 | 
				
			||||||
@@ -18631,6 +18916,11 @@
 | 
				
			|||||||
      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
 | 
					      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "typed-function": {
 | 
				
			||||||
 | 
					      "version": "2.1.0",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-2.1.0.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "typedarray": {
 | 
					    "typedarray": {
 | 
				
			||||||
      "version": "0.0.6",
 | 
					      "version": "0.0.6",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
 | 
				
			||||||
@@ -18802,14 +19092,14 @@
 | 
				
			|||||||
      "integrity": "sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ=="
 | 
					      "integrity": "sha512-nguyw8L6Un8eelg1vQ31vIU2ESxqid7EYmy8V+MDeMaHBqaRSkg3dTBToC1PR00D89UzS/SLkfYPnx0Wf23IQQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "vite": {
 | 
					    "vite": {
 | 
				
			||||||
      "version": "2.9.1",
 | 
					      "version": "2.9.9",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.9.tgz",
 | 
				
			||||||
      "integrity": "sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ==",
 | 
					      "integrity": "sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "esbuild": "^0.14.27",
 | 
					        "esbuild": "^0.14.27",
 | 
				
			||||||
        "fsevents": "~2.3.2",
 | 
					        "fsevents": "~2.3.2",
 | 
				
			||||||
        "postcss": "^8.4.12",
 | 
					        "postcss": "^8.4.13",
 | 
				
			||||||
        "resolve": "^1.22.0",
 | 
					        "resolve": "^1.22.0",
 | 
				
			||||||
        "rollup": "^2.59.0"
 | 
					        "rollup": "^2.59.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -18853,18 +19143,19 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "vitest": {
 | 
					    "vitest": {
 | 
				
			||||||
      "version": "0.5.9",
 | 
					      "version": "0.13.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.5.9.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.13.1.tgz",
 | 
				
			||||||
      "integrity": "sha512-R8lRP9Q1yIbwr8pDf2gvw4PFe8H5YMyHhBcdyfnUh6toLfCR10jrdI/WkNxdo5I4H/9XrMX9t+SAavdJExNdKg==",
 | 
					      "integrity": "sha512-CfSBf7YFw/i8HumSUQRtZKs0aV91DC9WU8nAgIJAlawKHaFuPHQohDwOTPIFgrxySiuFYUa0Yohf9gDFfBwjxA==",
 | 
				
			||||||
      "dev": true,
 | 
					      "dev": true,
 | 
				
			||||||
      "requires": {
 | 
					      "requires": {
 | 
				
			||||||
        "@types/chai": "^4.3.0",
 | 
					        "@types/chai": "^4.3.1",
 | 
				
			||||||
        "@types/chai-subset": "^1.3.3",
 | 
					        "@types/chai-subset": "^1.3.3",
 | 
				
			||||||
        "chai": "^4.3.6",
 | 
					        "chai": "^4.3.6",
 | 
				
			||||||
 | 
					        "debug": "^4.3.4",
 | 
				
			||||||
        "local-pkg": "^0.4.1",
 | 
					        "local-pkg": "^0.4.1",
 | 
				
			||||||
        "tinypool": "^0.1.2",
 | 
					        "tinypool": "^0.1.3",
 | 
				
			||||||
        "tinyspy": "^0.3.0",
 | 
					        "tinyspy": "^0.3.2",
 | 
				
			||||||
        "vite": "^2.7.10"
 | 
					        "vite": "^2.9.9"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "void-elements": {
 | 
					    "void-elements": {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "it-tools",
 | 
					  "name": "it-tools",
 | 
				
			||||||
  "version": "2.1.0",
 | 
					  "version": "2.5.0",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "dev": "vite",
 | 
					    "dev": "vite",
 | 
				
			||||||
    "build": "vue-tsc --noEmit && vite build",
 | 
					    "build": "vue-tsc --noEmit && vite build",
 | 
				
			||||||
@@ -25,7 +25,10 @@
 | 
				
			|||||||
    "cronstrue": "^2.2.0",
 | 
					    "cronstrue": "^2.2.0",
 | 
				
			||||||
    "crypto-js": "^4.1.1",
 | 
					    "crypto-js": "^4.1.1",
 | 
				
			||||||
    "date-fns": "^2.28.0",
 | 
					    "date-fns": "^2.28.0",
 | 
				
			||||||
 | 
					    "figue": "^1.2.0",
 | 
				
			||||||
 | 
					    "highlight.js": "^11.5.1",
 | 
				
			||||||
    "lodash": "^4.17.21",
 | 
					    "lodash": "^4.17.21",
 | 
				
			||||||
 | 
					    "mathjs": "^10.6.0",
 | 
				
			||||||
    "naive-ui": "^2.28.0",
 | 
					    "naive-ui": "^2.28.0",
 | 
				
			||||||
    "pinia": "^2.0.11",
 | 
					    "pinia": "^2.0.11",
 | 
				
			||||||
    "plausible-tracker": "^0.3.5",
 | 
					    "plausible-tracker": "^0.3.5",
 | 
				
			||||||
@@ -46,6 +49,7 @@
 | 
				
			|||||||
    "@types/uuid": "^8.3.4",
 | 
					    "@types/uuid": "^8.3.4",
 | 
				
			||||||
    "@vitejs/plugin-vue": "^2.2.2",
 | 
					    "@vitejs/plugin-vue": "^2.2.2",
 | 
				
			||||||
    "@vitejs/plugin-vue-jsx": "^1.3.7",
 | 
					    "@vitejs/plugin-vue-jsx": "^1.3.7",
 | 
				
			||||||
 | 
					    "@vue/eslint-config-prettier": "^7.0.0",
 | 
				
			||||||
    "@vue/eslint-config-typescript": "^10.0.0",
 | 
					    "@vue/eslint-config-typescript": "^10.0.0",
 | 
				
			||||||
    "@vue/test-utils": "^2.0.0-rc.18",
 | 
					    "@vue/test-utils": "^2.0.0-rc.18",
 | 
				
			||||||
    "@vue/tsconfig": "^0.1.3",
 | 
					    "@vue/tsconfig": "^0.1.3",
 | 
				
			||||||
@@ -54,7 +58,7 @@
 | 
				
			|||||||
    "eslint-plugin-vue": "^8.2.0",
 | 
					    "eslint-plugin-vue": "^8.2.0",
 | 
				
			||||||
    "jsdom": "^19.0.0",
 | 
					    "jsdom": "^19.0.0",
 | 
				
			||||||
    "less": "^4.1.2",
 | 
					    "less": "^4.1.2",
 | 
				
			||||||
    "prettier": "^2.5.1",
 | 
					    "prettier": "^2.6.2",
 | 
				
			||||||
    "standard-version": "^9.3.2",
 | 
					    "standard-version": "^9.3.2",
 | 
				
			||||||
    "start-server-and-test": "^1.14.0",
 | 
					    "start-server-and-test": "^1.14.0",
 | 
				
			||||||
    "typescript": "~4.5.5",
 | 
					    "typescript": "~4.5.5",
 | 
				
			||||||
@@ -62,7 +66,7 @@
 | 
				
			|||||||
    "vite-plugin-md": "^0.12.4",
 | 
					    "vite-plugin-md": "^0.12.4",
 | 
				
			||||||
    "vite-plugin-pwa": "^0.11.13",
 | 
					    "vite-plugin-pwa": "^0.11.13",
 | 
				
			||||||
    "vite-svg-loader": "^3.2.0",
 | 
					    "vite-svg-loader": "^3.2.0",
 | 
				
			||||||
    "vitest": "^0.5.0",
 | 
					    "vitest": "^0.13.1",
 | 
				
			||||||
    "vue-tsc": "^0.31.4"
 | 
					    "vue-tsc": "^0.31.4"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,16 +39,16 @@ createToolFile(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
`
 | 
					`,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
createToolFile(
 | 
					createToolFile(
 | 
				
			||||||
  `index.ts`,
 | 
					  `index.ts`,
 | 
				
			||||||
  `
 | 
					  `
 | 
				
			||||||
import { ArrowsShuffle } from '@vicons/tabler';
 | 
					import { ArrowsShuffle } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: '${toolNameTitleCase}',
 | 
					  name: '${toolNameTitleCase}',
 | 
				
			||||||
  path: '/${toolName}',
 | 
					  path: '/${toolName}',
 | 
				
			||||||
  description: '',
 | 
					  description: '',
 | 
				
			||||||
@@ -56,7 +56,7 @@ export const tool: ITool = {
 | 
				
			|||||||
  component: () => import('./${toolName}.vue'),
 | 
					  component: () => import('./${toolName}.vue'),
 | 
				
			||||||
  icon: ArrowsShuffle,
 | 
					  icon: ArrowsShuffle,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
`
 | 
					`,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
createToolFile(`${toolName}.service.ts`, ``);
 | 
					createToolFile(`${toolName}.service.ts`, ``);
 | 
				
			||||||
@@ -69,7 +69,7 @@ import { expect, describe, it } from 'vitest';
 | 
				
			|||||||
// describe('${toolName}', () => {
 | 
					// describe('${toolName}', () => {
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// })
 | 
					// })
 | 
				
			||||||
`
 | 
					`,
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toolsIndex = join(toolsDir, 'index.ts');
 | 
					const toolsIndex = join(toolsDir, 'index.ts');
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										19
									
								
								src/App.vue
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								src/App.vue
									
									
									
									
									
								
							@@ -1,24 +1,21 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { layouts } from './layouts';
 | 
					import { layouts } from './layouts';
 | 
				
			||||||
import { computed } from 'vue';
 | 
					import { computed } from 'vue';
 | 
				
			||||||
import { useRoute, RouterView } from 'vue-router'
 | 
					import { useRoute, RouterView } from 'vue-router';
 | 
				
			||||||
import { darkThemeOverrides, lightThemeOverrides } from './themes'
 | 
					import { darkThemeOverrides, lightThemeOverrides } from './themes';
 | 
				
			||||||
import { darkTheme, NGlobalStyle, NMessageProvider } from 'naive-ui'
 | 
					import { darkTheme, NGlobalStyle, NMessageProvider } from 'naive-ui';
 | 
				
			||||||
import { useStyleStore } from './stores/style.store';
 | 
					import { useStyleStore } from './stores/style.store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					const route = useRoute();
 | 
				
			||||||
const layout = computed(() => route?.meta?.layout ?? layouts.base)
 | 
					const layout = computed(() => route?.meta?.layout ?? layouts.base);
 | 
				
			||||||
const styleStore = useStyleStore()
 | 
					const styleStore = useStyleStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const theme = computed(() => styleStore.isDarkTheme ? darkTheme : null)
 | 
					const theme = computed(() => (styleStore.isDarkTheme ? darkTheme : null));
 | 
				
			||||||
const themeOverrides = computed(() => styleStore.isDarkTheme ? darkThemeOverrides : lightThemeOverrides)
 | 
					const themeOverrides = computed(() => (styleStore.isDarkTheme ? darkThemeOverrides : lightThemeOverrides));
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-config-provider
 | 
					  <n-config-provider :theme="theme" :theme-overrides="themeOverrides">
 | 
				
			||||||
    :theme="theme"
 | 
					 | 
				
			||||||
    :theme-overrides="themeOverrides"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <n-global-style />
 | 
					    <n-global-style />
 | 
				
			||||||
    <n-message-provider placement="bottom">
 | 
					    <n-message-provider placement="bottom">
 | 
				
			||||||
      <component :is="layout">
 | 
					      <component :is="layout">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,7 @@
 | 
				
			|||||||
    <template #suffix>
 | 
					    <template #suffix>
 | 
				
			||||||
      <n-tooltip trigger="hover">
 | 
					      <n-tooltip trigger="hover">
 | 
				
			||||||
        <template #trigger>
 | 
					        <template #trigger>
 | 
				
			||||||
          <n-button
 | 
					          <n-button quaternary circle @click="onCopyClicked">
 | 
				
			||||||
            quaternary
 | 
					 | 
				
			||||||
            circle
 | 
					 | 
				
			||||||
            @click="onCopyClicked"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <n-icon :component="ContentCopyFilled" />
 | 
					            <n-icon :component="ContentCopyFilled" />
 | 
				
			||||||
          </n-button>
 | 
					          </n-button>
 | 
				
			||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
@@ -18,25 +14,25 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useVModel, useClipboard } from '@vueuse/core'
 | 
					import { useVModel, useClipboard } from '@vueuse/core';
 | 
				
			||||||
import { ContentCopyFilled } from '@vicons/material'
 | 
					import { ContentCopyFilled } from '@vicons/material';
 | 
				
			||||||
import { ref } from 'vue';
 | 
					import { ref } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = defineProps<{ value: string, }>()
 | 
					const props = defineProps<{ value: string }>();
 | 
				
			||||||
const emit = defineEmits(['update:value'])
 | 
					const emit = defineEmits(['update:value']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const value = useVModel(props, 'value', emit)
 | 
					const value = useVModel(props, 'value', emit);
 | 
				
			||||||
const tooltipText = ref('Copy to clipboard')
 | 
					const tooltipText = ref('Copy to clipboard');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {copy} = useClipboard({source: value})
 | 
					const { copy } = useClipboard({ source: value });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onCopyClicked() {
 | 
					function onCopyClicked() {
 | 
				
			||||||
  copy();
 | 
					  copy();
 | 
				
			||||||
    tooltipText.value = 'Copied !'
 | 
					  tooltipText.value = 'Copied !';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setTimeout(() => {
 | 
					  setTimeout(() => {
 | 
				
			||||||
        tooltipText.value = 'Copy to clipboard'
 | 
					    tooltipText.value = 'Copy to clipboard';
 | 
				
			||||||
    }, 2000)
 | 
					  }, 2000);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								src/components/MenuIconItem.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/components/MenuIconItem.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="menu-icon-item">
 | 
				
			||||||
 | 
					    <n-icon :component="tool.icon" />
 | 
				
			||||||
 | 
					    <div v-if="tool.isNew" class="badge"></div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import type { ITool } from '@/tools/tool';
 | 
				
			||||||
 | 
					import { useThemeVars } from 'naive-ui';
 | 
				
			||||||
 | 
					import { toRefs } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps<{ tool: ITool }>();
 | 
				
			||||||
 | 
					const { tool } = toRefs(props);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const theme = useThemeVars();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 | 
					.menu-icon-item {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .badge {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    background-color: v-bind('theme.primaryColor');
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    line-height: 1;
 | 
				
			||||||
 | 
					    top: 3px;
 | 
				
			||||||
 | 
					    left: -6px;
 | 
				
			||||||
 | 
					    font-size: 10px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    height: 6px;
 | 
				
			||||||
 | 
					    width: 6px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -14,11 +14,7 @@
 | 
				
			|||||||
    </n-layout-sider>
 | 
					    </n-layout-sider>
 | 
				
			||||||
    <n-layout class="content">
 | 
					    <n-layout class="content">
 | 
				
			||||||
      <slot name="content" />
 | 
					      <slot name="content" />
 | 
				
			||||||
      <div
 | 
					      <div v-show="isSmallScreen && !isMenuCollapsed" class="overlay" @click="isMenuCollapsed = true" />
 | 
				
			||||||
        v-show="isSmallScreen && !isMenuCollapsed"
 | 
					 | 
				
			||||||
        class="overlay"
 | 
					 | 
				
			||||||
        @click="isMenuCollapsed = true"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </n-layout>
 | 
					    </n-layout>
 | 
				
			||||||
  </n-layout>
 | 
					  </n-layout>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
@@ -27,9 +23,9 @@
 | 
				
			|||||||
import { useStyleStore } from '@/stores/style.store';
 | 
					import { useStyleStore } from '@/stores/style.store';
 | 
				
			||||||
import { toRefs, computed } from 'vue';
 | 
					import { toRefs, computed } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const styleStore = useStyleStore()
 | 
					const styleStore = useStyleStore();
 | 
				
			||||||
const { isMenuCollapsed, isSmallScreen } = toRefs(styleStore)
 | 
					const { isMenuCollapsed, isSmallScreen } = toRefs(styleStore);
 | 
				
			||||||
const siderPosition = computed(() => isSmallScreen.value ? 'absolute' : 'static')
 | 
					const siderPosition = computed(() => (isSmallScreen.value ? 'absolute' : 'static'));
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
@@ -44,7 +40,6 @@ const siderPosition = computed(() => isSmallScreen.value ? 'absolute' : 'static'
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.content {
 | 
					.content {
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // background-color: #f1f5f9;
 | 
					  // background-color: #f1f5f9;
 | 
				
			||||||
  ::v-deep(.n-layout-scroll-container) {
 | 
					  ::v-deep(.n-layout-scroll-container) {
 | 
				
			||||||
    padding: 26px;
 | 
					    padding: 26px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,10 +10,7 @@
 | 
				
			|||||||
        rel="noopener"
 | 
					        rel="noopener"
 | 
				
			||||||
        target="_blank"
 | 
					        target="_blank"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <n-icon
 | 
					        <n-icon size="25" :component="BrandGithub" />
 | 
				
			||||||
          size="25"
 | 
					 | 
				
			||||||
          :component="BrandGithub"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-button>
 | 
					      </n-button>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    Github repository
 | 
					    Github repository
 | 
				
			||||||
@@ -26,40 +23,21 @@
 | 
				
			|||||||
        circle
 | 
					        circle
 | 
				
			||||||
        quaternary
 | 
					        quaternary
 | 
				
			||||||
        tag="a"
 | 
					        tag="a"
 | 
				
			||||||
        href="https://twitter.com/cthmsst"
 | 
					        href="https://twitter.com/ittoolsdottech"
 | 
				
			||||||
        rel="noopener"
 | 
					        rel="noopener"
 | 
				
			||||||
        target="_blank"
 | 
					        target="_blank"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <n-icon
 | 
					        <n-icon size="25" :component="BrandTwitter" />
 | 
				
			||||||
          size="25"
 | 
					 | 
				
			||||||
          :component="BrandTwitter"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-button>
 | 
					      </n-button>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    Creator twitter
 | 
					    Creator twitter
 | 
				
			||||||
  </n-tooltip>
 | 
					  </n-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <router-link to="/about" #="{ navigate, href }" custom>
 | 
				
			||||||
  <router-link
 | 
					 | 
				
			||||||
    to="/about"
 | 
					 | 
				
			||||||
    #="{ navigate, href }"
 | 
					 | 
				
			||||||
    custom
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <n-tooltip trigger="hover">
 | 
					    <n-tooltip trigger="hover">
 | 
				
			||||||
      <template #trigger>
 | 
					      <template #trigger>
 | 
				
			||||||
        <n-button
 | 
					        <n-button tag="a" :href="href" circle quaternary size="large" aria-label="Home" @click="navigate">
 | 
				
			||||||
          tag="a"
 | 
					          <n-icon size="25" :component="InfoCircle" />
 | 
				
			||||||
          :href="href"
 | 
					 | 
				
			||||||
          circle
 | 
					 | 
				
			||||||
          quaternary
 | 
					 | 
				
			||||||
          size="large"
 | 
					 | 
				
			||||||
          aria-label="Home"
 | 
					 | 
				
			||||||
          @click="navigate"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <n-icon
 | 
					 | 
				
			||||||
            size="25"
 | 
					 | 
				
			||||||
            :component="InfoCircle"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </n-button>
 | 
					        </n-button>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
      About
 | 
					      About
 | 
				
			||||||
@@ -67,22 +45,9 @@
 | 
				
			|||||||
  </router-link>
 | 
					  </router-link>
 | 
				
			||||||
  <n-tooltip trigger="hover">
 | 
					  <n-tooltip trigger="hover">
 | 
				
			||||||
    <template #trigger>
 | 
					    <template #trigger>
 | 
				
			||||||
      <n-button
 | 
					      <n-button size="large" circle quaternary @click="isDarkTheme = !isDarkTheme">
 | 
				
			||||||
        size="large"
 | 
					        <n-icon v-if="isDarkTheme" size="25" :component="Sun" />
 | 
				
			||||||
        circle
 | 
					        <n-icon v-else size="25" :component="Moon" />
 | 
				
			||||||
        quaternary
 | 
					 | 
				
			||||||
        @click="isDarkTheme = !isDarkTheme"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-icon
 | 
					 | 
				
			||||||
          v-if="isDarkTheme"
 | 
					 | 
				
			||||||
          size="25"
 | 
					 | 
				
			||||||
          :component="Sun"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <n-icon
 | 
					 | 
				
			||||||
          v-else
 | 
					 | 
				
			||||||
          size="25"
 | 
					 | 
				
			||||||
          :component="Moon"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-button>
 | 
					      </n-button>
 | 
				
			||||||
    </template>
 | 
					    </template>
 | 
				
			||||||
    <span v-if="isDarkTheme">Light mode</span>
 | 
					    <span v-if="isDarkTheme">Light mode</span>
 | 
				
			||||||
@@ -93,10 +58,10 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useStyleStore } from '@/stores/style.store';
 | 
					import { useStyleStore } from '@/stores/style.store';
 | 
				
			||||||
import { toRefs } from 'vue';
 | 
					import { toRefs } from 'vue';
 | 
				
			||||||
import { BrandGithub, BrandTwitter, Moon, Sun, InfoCircle } from '@vicons/tabler'
 | 
					import { BrandGithub, BrandTwitter, Moon, Sun, InfoCircle } from '@vicons/tabler';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const styleStore = useStyleStore()
 | 
					const styleStore = useStyleStore();
 | 
				
			||||||
const { isDarkTheme } = toRefs(styleStore)
 | 
					const { isDarkTheme } = toRefs(styleStore);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,34 +1,33 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { SearchRound } from '@vicons/material'
 | 
					import { SearchRound } from '@vicons/material';
 | 
				
			||||||
import { computed, ref } from 'vue';
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
import { deburr } from 'lodash'
 | 
					import { deburr } from 'lodash';
 | 
				
			||||||
import { tools } from '@/tools';
 | 
					import { tools } from '@/tools';
 | 
				
			||||||
import { useRouter } from 'vue-router';
 | 
					import { useRouter } from 'vue-router';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = useRouter()
 | 
					const router = useRouter();
 | 
				
			||||||
const queryString = ref('')
 | 
					const queryString = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cleanString = (s: string) => deburr(s.trim().toLowerCase())
 | 
					const cleanString = (s: string) => deburr(s.trim().toLowerCase());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchableTools = tools.map(({ name, description, keywords, path }) => ({
 | 
					const searchableTools = tools.map(({ name, description, keywords, path }) => ({
 | 
				
			||||||
  searchableText: [name, description, ...keywords].map(cleanString).join(' '),
 | 
					  searchableText: [name, description, ...keywords].map(cleanString).join(' '),
 | 
				
			||||||
  path,
 | 
					  path,
 | 
				
			||||||
    name
 | 
					  name,
 | 
				
			||||||
}))
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const options = computed(() => {
 | 
					const options = computed(() => {
 | 
				
			||||||
    const query = cleanString(queryString.value)
 | 
					  const query = cleanString(queryString.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return searchableTools
 | 
					  return searchableTools
 | 
				
			||||||
    .filter(({ searchableText }) => searchableText.includes(query))
 | 
					    .filter(({ searchableText }) => searchableText.includes(query))
 | 
				
			||||||
        .map(({ name, path }) => ({ label: name, value: path }))
 | 
					    .map(({ name, path }) => ({ label: name, value: path }));
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onSelect(path: string) {
 | 
					function onSelect(path: string) {
 | 
				
			||||||
    router.push(path)
 | 
					  router.push(path);
 | 
				
			||||||
    queryString.value = ''
 | 
					  queryString.value = '';
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
@@ -58,7 +57,6 @@ function onSelect(path: string) {
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
// ::v-deep(.n-input__border) {
 | 
					// ::v-deep(.n-input__border) {
 | 
				
			||||||
//     border: none;
 | 
					//     border: none;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,26 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <router-link :to="tool.path">
 | 
					  <router-link :to="tool.path">
 | 
				
			||||||
    <n-card class="tool-card">
 | 
					    <n-card class="tool-card">
 | 
				
			||||||
      <n-icon
 | 
					      <n-space justify="space-between" align="center">
 | 
				
			||||||
        class="icon"
 | 
					        <n-icon class="icon" size="40" :component="tool.icon" />
 | 
				
			||||||
        size="40"
 | 
					        <n-tag
 | 
				
			||||||
        :component="tool.icon"
 | 
					          v-if="tool.isNew"
 | 
				
			||||||
      />
 | 
					          size="small"
 | 
				
			||||||
 | 
					          class="badge-new"
 | 
				
			||||||
 | 
					          round
 | 
				
			||||||
 | 
					          type="success"
 | 
				
			||||||
 | 
					          :bordered="false"
 | 
				
			||||||
 | 
					          :color="{ color: theme.primaryColor, textColor: theme.tagColor }"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          New
 | 
				
			||||||
 | 
					        </n-tag>
 | 
				
			||||||
 | 
					      </n-space>
 | 
				
			||||||
      <n-h3 class="title">
 | 
					      <n-h3 class="title">
 | 
				
			||||||
        <n-ellipsis>{{ tool.name }}</n-ellipsis>
 | 
					        <n-ellipsis>{{ tool.name }}</n-ellipsis>
 | 
				
			||||||
      </n-h3>
 | 
					      </n-h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="description">
 | 
					      <div class="description">
 | 
				
			||||||
        <n-ellipsis
 | 
					        <n-ellipsis :line-clamp="2" :tooltip="false">
 | 
				
			||||||
          :line-clamp="2"
 | 
					 | 
				
			||||||
          :tooltip="false"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          {{ tool.description }}
 | 
					          {{ tool.description }}
 | 
				
			||||||
        </n-ellipsis>
 | 
					        </n-ellipsis>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@@ -22,12 +29,13 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import type { ITool } from '@/tools/Tool';
 | 
					import type { ITool } from '@/tools/tool';
 | 
				
			||||||
import { toRefs, defineProps } from 'vue';
 | 
					import { useThemeVars } from 'naive-ui';
 | 
				
			||||||
 | 
					import { toRefs } from 'vue';
 | 
				
			||||||
const props = defineProps<{ tool: ITool & { category: string } }>()
 | 
					 | 
				
			||||||
const { tool } = toRefs(props)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = defineProps<{ tool: ITool & { category: string } }>();
 | 
				
			||||||
 | 
					const { tool } = toRefs(props);
 | 
				
			||||||
 | 
					const theme = useThemeVars();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,14 @@ type UseValidationRule<T> = {
 | 
				
			|||||||
  message: string;
 | 
					  message: string;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isFalsyOrHasThrown(cb: () => boolean) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return !cb();
 | 
				
			||||||
 | 
					  } catch (_) {
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useValidation<T>({ source, rules }: { source: Ref<T>; rules: UseValidationRule<T>[] }) {
 | 
					export function useValidation<T>({ source, rules }: { source: Ref<T>; rules: UseValidationRule<T>[] }) {
 | 
				
			||||||
  const state = reactive<{
 | 
					  const state = reactive<{
 | 
				
			||||||
    message: string;
 | 
					    message: string;
 | 
				
			||||||
@@ -19,7 +27,7 @@ export function useValidation<T>({ source, rules }: { source: Ref<T>; rules: Use
 | 
				
			|||||||
    state.status = undefined;
 | 
					    state.status = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const rule of rules) {
 | 
					    for (const rule of rules) {
 | 
				
			||||||
      if (!rule.validator(source.value)) {
 | 
					      if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
 | 
				
			||||||
        state.message = rule.message;
 | 
					        state.message = rule.message;
 | 
				
			||||||
        state.status = 'error';
 | 
					        state.status = 'error';
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										65
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					import { figue } from 'figue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const config = figue({
 | 
				
			||||||
 | 
					  app: {
 | 
				
			||||||
 | 
					    version: {
 | 
				
			||||||
 | 
					      doc: 'Application current version',
 | 
				
			||||||
 | 
					      format: 'string',
 | 
				
			||||||
 | 
					      default: '0.0.0',
 | 
				
			||||||
 | 
					      env: 'PACKAGE_VERSION',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    lastCommitSha: {
 | 
				
			||||||
 | 
					      doc: 'Application last commit SHA version',
 | 
				
			||||||
 | 
					      format: 'string',
 | 
				
			||||||
 | 
					      default: '',
 | 
				
			||||||
 | 
					      env: 'VITE_VERCEL_GIT_COMMIT_SHA',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    baseUrl: {
 | 
				
			||||||
 | 
					      doc: 'Application base url',
 | 
				
			||||||
 | 
					      format: 'string',
 | 
				
			||||||
 | 
					      default: '/',
 | 
				
			||||||
 | 
					      env: 'BASE_URL',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    env: {
 | 
				
			||||||
 | 
					      doc: 'Application current env',
 | 
				
			||||||
 | 
					      format: 'enum',
 | 
				
			||||||
 | 
					      values: ['production', 'development', 'test'],
 | 
				
			||||||
 | 
					      default: 'development',
 | 
				
			||||||
 | 
					      env: 'MODE',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  plausible: {
 | 
				
			||||||
 | 
					    domain: {
 | 
				
			||||||
 | 
					      doc: 'Plausible current domain',
 | 
				
			||||||
 | 
					      format: 'string',
 | 
				
			||||||
 | 
					      default: '',
 | 
				
			||||||
 | 
					      env: 'VITE_PLAUSIBLE_DOMAIN',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    apiHost: {
 | 
				
			||||||
 | 
					      doc: 'Plausible remote api host',
 | 
				
			||||||
 | 
					      format: 'string',
 | 
				
			||||||
 | 
					      default: '',
 | 
				
			||||||
 | 
					      env: 'VITE_PLAUSIBLE_API_HOST',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    trackLocalhost: {
 | 
				
			||||||
 | 
					      doc: 'Enable or disable localhost tracking by plausible',
 | 
				
			||||||
 | 
					      format: 'boolean',
 | 
				
			||||||
 | 
					      default: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  tools: {
 | 
				
			||||||
 | 
					    newTools: {
 | 
				
			||||||
 | 
					      doc: 'Tool names for tools flagged a as new',
 | 
				
			||||||
 | 
					      format: 'array',
 | 
				
			||||||
 | 
					      default: [],
 | 
				
			||||||
 | 
					      env: 'VITE_NEW_TOOLS',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					  .loadEnv({
 | 
				
			||||||
 | 
					    ...import.meta.env,
 | 
				
			||||||
 | 
					    // Because the string 'import.meta.env.PACKAGE_VERSION' is statically replaced during build time (see 'define' in vite.config.ts)
 | 
				
			||||||
 | 
					    PACKAGE_VERSION: import.meta.env.PACKAGE_VERSION 
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .validate()
 | 
				
			||||||
 | 
					  .getConfig();
 | 
				
			||||||
@@ -1,75 +1,62 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { NIcon, useThemeVars } from 'naive-ui';
 | 
					import { NIcon, useThemeVars } from 'naive-ui';
 | 
				
			||||||
import { h, ref, type Component } from 'vue';
 | 
					import { h } from 'vue';
 | 
				
			||||||
import { RouterLink, useRoute } from 'vue-router';
 | 
					import { RouterLink, useRoute } from 'vue-router';
 | 
				
			||||||
import { Heart, Menu2, Home2 } from '@vicons/tabler'
 | 
					import { Heart, Menu2, Home2 } from '@vicons/tabler';
 | 
				
			||||||
import { toolsByCategory } from '@/tools';
 | 
					import { toolsByCategory } from '@/tools';
 | 
				
			||||||
import SearchBar from '../components/SearchBar.vue';
 | 
					import SearchBar from '../components/SearchBar.vue';
 | 
				
			||||||
import { useStyleStore } from '@/stores/style.store';
 | 
					import { useStyleStore } from '@/stores/style.store';
 | 
				
			||||||
import HeroGradient from '../assets/hero-gradient.svg?component'
 | 
					import HeroGradient from '../assets/hero-gradient.svg?component';
 | 
				
			||||||
import MenuLayout from '../components/MenuLayout.vue'
 | 
					import MenuLayout from '../components/MenuLayout.vue';
 | 
				
			||||||
import NavbarButtons from '../components/NavbarButtons.vue'
 | 
					import NavbarButtons from '../components/NavbarButtons.vue';
 | 
				
			||||||
 | 
					import { config } from '@/config';
 | 
				
			||||||
 | 
					import MenuIconItem from '@/components/MenuIconItem.vue';
 | 
				
			||||||
 | 
					import type { ITool } from '@/tools/tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const themeVars = useThemeVars()
 | 
					const themeVars = useThemeVars();
 | 
				
			||||||
const activeKey = ref(null)
 | 
					const route = useRoute();
 | 
				
			||||||
const route = useRoute()
 | 
					const styleStore = useStyleStore();
 | 
				
			||||||
const styleStore = useStyleStore()
 | 
					const version = config.app.version;
 | 
				
			||||||
const version = import.meta.env.PACKAGE_VERSION;
 | 
					const commitSha = config.app.lastCommitSha.slice(0, 7);
 | 
				
			||||||
const commitSha = import.meta.env.GIT_SHORT_SHA;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const makeLabel = (text: string, to: string) => () => h(RouterLink, { to }, { default: () => text })
 | 
					const makeLabel = (text: string, to: string) => () => h(RouterLink, { to }, { default: () => text });
 | 
				
			||||||
const makeIcon = (icon: Component) => () => h(NIcon, null, { default: () => h(icon) })
 | 
					const makeIcon = (tool: ITool) => () => h(MenuIconItem, { tool });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const m = toolsByCategory.map(category => ({
 | 
					const menuOptions = toolsByCategory.map((category) => ({
 | 
				
			||||||
  label: category.name,
 | 
					  label: category.name,
 | 
				
			||||||
  key: category.name,
 | 
					  key: category.name,
 | 
				
			||||||
  type: 'group',
 | 
					  type: 'group',
 | 
				
			||||||
  children: category.components.map(({ name, path, icon }) => ({
 | 
					  children: category.components.map((tool) => ({
 | 
				
			||||||
    label: makeLabel(name, path),
 | 
					    label: makeLabel(tool.name, tool.path),
 | 
				
			||||||
    icon: makeIcon(icon),
 | 
					    icon: makeIcon(tool),
 | 
				
			||||||
    key: name
 | 
					    key: name,
 | 
				
			||||||
  }))
 | 
					  })),
 | 
				
			||||||
}))
 | 
					}));
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <menu-layout
 | 
					  <menu-layout class="menu-layout" :class="{ isSmallScreen: styleStore.isSmallScreen }">
 | 
				
			||||||
    class="menu-layout"
 | 
					 | 
				
			||||||
    :class="{ isSmallScreen: styleStore.isSmallScreen }"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <template #sider>
 | 
					    <template #sider>
 | 
				
			||||||
      <router-link
 | 
					      <router-link to="/" class="hero-wrapper">
 | 
				
			||||||
        to="/"
 | 
					 | 
				
			||||||
        class="hero-wrapper"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <hero-gradient class="gradient" />
 | 
					        <hero-gradient class="gradient" />
 | 
				
			||||||
        <div class="text-wrapper">
 | 
					        <div class="text-wrapper">
 | 
				
			||||||
          <div class="title">
 | 
					          <div class="title">IT - TOOLS</div>
 | 
				
			||||||
            IT - TOOLS
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
          <div class="divider" />
 | 
					          <div class="divider" />
 | 
				
			||||||
          <div class="subtitle">
 | 
					          <div class="subtitle">Handy tools for developers</div>
 | 
				
			||||||
            Handy tools for developers
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </router-link>
 | 
					      </router-link>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="sider-content">
 | 
					      <div class="sider-content">
 | 
				
			||||||
        <n-space
 | 
					        <n-space v-if="styleStore.isSmallScreen" justify="center">
 | 
				
			||||||
          v-if="styleStore.isSmallScreen"
 | 
					 | 
				
			||||||
          justify="center"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <navbar-buttons />
 | 
					          <navbar-buttons />
 | 
				
			||||||
        </n-space>
 | 
					        </n-space>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <n-menu
 | 
					        <n-menu
 | 
				
			||||||
          v-model:value="activeKey"
 | 
					 | 
				
			||||||
          :value="route.name"
 | 
					 | 
				
			||||||
          class="menu"
 | 
					          class="menu"
 | 
				
			||||||
 | 
					          :value="(route.name as string)"
 | 
				
			||||||
          :collapsed-width="64"
 | 
					          :collapsed-width="64"
 | 
				
			||||||
          :collapsed-icon-size="22"
 | 
					          :collapsed-icon-size="22"
 | 
				
			||||||
          :options="m"
 | 
					          :options="menuOptions"
 | 
				
			||||||
          :indent="20"
 | 
					          :indent="20"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,7 +85,7 @@ const m = toolsByCategory.map(category => ({
 | 
				
			|||||||
                rel="noopener"
 | 
					                rel="noopener"
 | 
				
			||||||
                type="primary"
 | 
					                type="primary"
 | 
				
			||||||
                depth="3"
 | 
					                depth="3"
 | 
				
			||||||
                :href="`https://github.com/CorentinTh/it-tools/tree/v${commitSha}`"
 | 
					                :href="`https://github.com/CorentinTh/it-tools/tree/${commitSha}`"
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                {{ commitSha }}
 | 
					                {{ commitSha }}
 | 
				
			||||||
              </n-button>
 | 
					              </n-button>
 | 
				
			||||||
@@ -106,13 +93,7 @@ const m = toolsByCategory.map(category => ({
 | 
				
			|||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div>
 | 
					          <div>
 | 
				
			||||||
            © {{ new Date().getFullYear() }}
 | 
					            © {{ new Date().getFullYear() }}
 | 
				
			||||||
            <n-button
 | 
					            <n-button text tag="a" target="_blank" rel="noopener" type="primary" href="https://github.com/CorentinTh">
 | 
				
			||||||
              text
 | 
					 | 
				
			||||||
              tag="a"
 | 
					 | 
				
			||||||
              target="_blank"
 | 
					 | 
				
			||||||
              rel="noopener"
 | 
					 | 
				
			||||||
              type="primary"
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
              Corentin Thomasset
 | 
					              Corentin Thomasset
 | 
				
			||||||
            </n-button>
 | 
					            </n-button>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@@ -129,18 +110,10 @@ const m = toolsByCategory.map(category => ({
 | 
				
			|||||||
          aria-label="Toogle menu"
 | 
					          aria-label="Toogle menu"
 | 
				
			||||||
          @click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
 | 
					          @click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <n-icon
 | 
					          <n-icon size="25" :component="Menu2" />
 | 
				
			||||||
            size="25"
 | 
					 | 
				
			||||||
            :component="Menu2"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </n-button>
 | 
					        </n-button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <router-link to="/" #="{ navigate, href }" custom>
 | 
				
			||||||
        <router-link
 | 
					 | 
				
			||||||
          to="/"
 | 
					 | 
				
			||||||
          #="{ navigate, href }"
 | 
					 | 
				
			||||||
          custom
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <n-tooltip trigger="hover">
 | 
					          <n-tooltip trigger="hover">
 | 
				
			||||||
            <template #trigger>
 | 
					            <template #trigger>
 | 
				
			||||||
              <n-button
 | 
					              <n-button
 | 
				
			||||||
@@ -152,10 +125,7 @@ const m = toolsByCategory.map(category => ({
 | 
				
			|||||||
                aria-label="Home"
 | 
					                aria-label="Home"
 | 
				
			||||||
                @click="navigate"
 | 
					                @click="navigate"
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <n-icon
 | 
					                <n-icon size="25" :component="Home2" />
 | 
				
			||||||
                  size="25"
 | 
					 | 
				
			||||||
                  :component="Home2"
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
              </n-button>
 | 
					              </n-button>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
            Home
 | 
					            Home
 | 
				
			||||||
@@ -173,11 +143,7 @@ const m = toolsByCategory.map(category => ({
 | 
				
			|||||||
              rel="noopener"
 | 
					              rel="noopener"
 | 
				
			||||||
              target="_blank"
 | 
					              target="_blank"
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <n-icon
 | 
					              <n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-right: 5px" />
 | 
				
			||||||
                v-if="!styleStore.isSmallScreen"
 | 
					 | 
				
			||||||
                :component="Heart"
 | 
					 | 
				
			||||||
                style="margin-right: 5px;"
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
              Sponsor
 | 
					              Sponsor
 | 
				
			||||||
            </n-button>
 | 
					            </n-button>
 | 
				
			||||||
          </template>
 | 
					          </template>
 | 
				
			||||||
@@ -254,6 +220,11 @@ const m = toolsByCategory.map(category => ({
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ::v-deep(.n-menu-item-content-header) {
 | 
				
			||||||
 | 
					//   overflow: visible !important;
 | 
				
			||||||
 | 
					//   // overflow-x: hidden !important;
 | 
				
			||||||
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.navigation {
 | 
					.navigation {
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
@@ -264,7 +235,6 @@ const m = toolsByCategory.map(category => ({
 | 
				
			|||||||
    margin-right: 5px;
 | 
					    margin-right: 5px;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
  .search-bar {
 | 
					  .search-bar {
 | 
				
			||||||
    // width: 100%;
 | 
					    // width: 100%;
 | 
				
			||||||
    flex-grow: 1;
 | 
					    flex-grow: 1;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,52 +1,84 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { useRoute } from 'vue-router';
 | 
					import { useRoute } from 'vue-router';
 | 
				
			||||||
import BaseLayout from './base.layout.vue';
 | 
					import BaseLayout from './base.layout.vue';
 | 
				
			||||||
import { useHead } from '@vueuse/head'
 | 
					import { useHead } from '@vueuse/head';
 | 
				
			||||||
import type { HeadObject } from '@vueuse/head'
 | 
					import type { HeadObject } from '@vueuse/head';
 | 
				
			||||||
import { reactive } from 'vue';
 | 
					import { reactive } from 'vue';
 | 
				
			||||||
 | 
					import { useThemeVars } from 'naive-ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute()
 | 
					const route = useRoute();
 | 
				
			||||||
 | 
					const theme = useThemeVars();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const head = reactive<HeadObject>({
 | 
					const head = reactive<HeadObject>({
 | 
				
			||||||
  title: `${route.meta.name} - IT Tools`,
 | 
					  title: `${route.meta.name} - IT Tools`,
 | 
				
			||||||
  meta: [
 | 
					  meta: [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      name: 'description',
 | 
					      name: 'description',
 | 
				
			||||||
            content: route.meta.description
 | 
					      content: route.meta.description,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      name: 'keywords',
 | 
					      name: 'keywords',
 | 
				
			||||||
            content: route.meta.keywords
 | 
					      content: route.meta.keywords,
 | 
				
			||||||
        }
 | 
					    },
 | 
				
			||||||
    ]
 | 
					  ],
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
useHead(head)
 | 
					useHead(head);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <base-layout>
 | 
					  <base-layout>
 | 
				
			||||||
    <div class="tool-layout">
 | 
					    <div class="tool-layout">
 | 
				
			||||||
      <div class="tool-header">
 | 
					      <div class="tool-header">
 | 
				
			||||||
        <n-h1>{{ route.meta.name }}</n-h1>
 | 
					        <n-h1>
 | 
				
			||||||
 | 
					          {{ route.meta.name }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <n-tag
 | 
				
			||||||
 | 
					            v-if="route.meta.isNew"
 | 
				
			||||||
 | 
					            round
 | 
				
			||||||
 | 
					            type="success"
 | 
				
			||||||
 | 
					            :bordered="false"
 | 
				
			||||||
 | 
					            :color="{ color: theme.primaryColor, textColor: theme.tagColor }"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            New tool
 | 
				
			||||||
 | 
					          </n-tag>
 | 
				
			||||||
 | 
					          <!-- <span class="new-tool-badge">New !</span> -->
 | 
				
			||||||
 | 
					        </n-h1>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div class="separator" />
 | 
					        <div class="separator" />
 | 
				
			||||||
        <div class="description">
 | 
					        <div class="description">
 | 
				
			||||||
          {{ route.meta.description }}
 | 
					          {{ route.meta.description }}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="tool-content">
 | 
				
			||||||
      <slot />
 | 
					      <slot />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </base-layout>
 | 
					  </base-layout>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 | 
					.tool-content {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-direction: row;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  align-items: flex-start;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  gap: 16px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ::v-deep(& > *) {
 | 
				
			||||||
 | 
					    flex: 0 1 600px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.tool-layout {
 | 
					.tool-layout {
 | 
				
			||||||
    max-width: 700px;
 | 
					  max-width: 600px;
 | 
				
			||||||
  margin: 0 auto;
 | 
					  margin: 0 auto;
 | 
				
			||||||
  box-sizing: border-box;
 | 
					  box-sizing: border-box;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .tool-header {
 | 
					  .tool-header {
 | 
				
			||||||
    padding: 40px 0;
 | 
					    padding: 40px 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .n-h1 {
 | 
					    .n-h1 {
 | 
				
			||||||
      opacity: 0.9;
 | 
					      opacity: 0.9;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,36 +1,21 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { useHead } from '@vueuse/head';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					useHead({ title: 'Page not found - IT Tools' });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="e404-wrapper">
 | 
					  <div class="e404-wrapper">
 | 
				
			||||||
    <n-result
 | 
					    <n-result status="404" title="404 Not Found" description="Sorry, this page does not seem to extist">
 | 
				
			||||||
      status="404"
 | 
					 | 
				
			||||||
      title="404 Not Found"
 | 
					 | 
				
			||||||
      description="Sorry, this page does not seem to extist"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <template #footer>
 | 
					      <template #footer>
 | 
				
			||||||
        <router-link
 | 
					        <router-link to="/" #="{ navigate, href }" custom>
 | 
				
			||||||
          to="/"
 | 
					          <n-button tag="a" :href="href" secondary type="success" @click="navigate"> Back home </n-button>
 | 
				
			||||||
          #="{ navigate, href }"
 | 
					 | 
				
			||||||
          custom
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <n-button
 | 
					 | 
				
			||||||
            tag="a"
 | 
					 | 
				
			||||||
            :href="href"
 | 
					 | 
				
			||||||
            secondary
 | 
					 | 
				
			||||||
            type="success"
 | 
					 | 
				
			||||||
            @click="navigate"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            Back home
 | 
					 | 
				
			||||||
          </n-button>
 | 
					 | 
				
			||||||
        </router-link>
 | 
					        </router-link>
 | 
				
			||||||
      </template>
 | 
					      </template>
 | 
				
			||||||
    </n-result>
 | 
					    </n-result>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped>
 | 
					<style scoped>
 | 
				
			||||||
.e404-wrapper {
 | 
					.e404-wrapper {
 | 
				
			||||||
  padding-top: 150px;
 | 
					  padding-top: 150px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,7 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { useHead } from '@vueuse/head';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					useHead({ title: 'About - IT Tools' });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
@@ -6,18 +9,10 @@
 | 
				
			|||||||
    <n-h1>About</n-h1>
 | 
					    <n-h1>About</n-h1>
 | 
				
			||||||
    <n-p>
 | 
					    <n-p>
 | 
				
			||||||
      This wonderful website, made with ❤ by
 | 
					      This wonderful website, made with ❤ by
 | 
				
			||||||
      <n-button
 | 
					      <n-button text tag="a" href="https://github.com/CorentinTh" target="_blank" rel="noopener" type="primary">
 | 
				
			||||||
        text
 | 
					        Corentin Thomasset </n-button
 | 
				
			||||||
        tag="a"
 | 
					      >, aggregates useful tools for developer and people working in IT. If you find it usefull, please fell free to
 | 
				
			||||||
        href="https://github.com/CorentinTh"
 | 
					      share it to people you think may find it usefull too and dont forget to pin it in your shortcut bar !
 | 
				
			||||||
        target="_blank"
 | 
					 | 
				
			||||||
        rel="noopener"
 | 
					 | 
				
			||||||
        type="primary"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Corentin Thomasset
 | 
					 | 
				
			||||||
      </n-button>,
 | 
					 | 
				
			||||||
      aggregates useful tools for developer and people working in IT. If you find it usefull, please fell free to share
 | 
					 | 
				
			||||||
      it to people you think may find it usefull too and dont forget to pin it in your shortcut bar !
 | 
					 | 
				
			||||||
    </n-p>
 | 
					    </n-p>
 | 
				
			||||||
    <n-p>
 | 
					    <n-p>
 | 
				
			||||||
      IT Tools is opensource (under the MIT license) and free, and will always be, but it cost me money to host and
 | 
					      IT Tools is opensource (under the MIT license) and free, and will always be, but it cost me money to host and
 | 
				
			||||||
@@ -31,13 +26,14 @@
 | 
				
			|||||||
        rel="noopener"
 | 
					        rel="noopener"
 | 
				
			||||||
        target="_blank"
 | 
					        target="_blank"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        sponsoring me
 | 
					        sponsoring me </n-button
 | 
				
			||||||
      </n-button>. 
 | 
					      >.
 | 
				
			||||||
    </n-p>
 | 
					    </n-p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <n-h2>Technologies</n-h2>
 | 
					    <n-h2>Technologies</n-h2>
 | 
				
			||||||
    <n-p>
 | 
					    <n-p>
 | 
				
			||||||
      IT Tools is made in Vue JS (vue 3) with the the naive-ui component library and is hosted and continuously deployed by Vercel. Third party opensource libraries are used in some tools, you may find the complete list in the 
 | 
					      IT Tools is made in Vue JS (vue 3) with the the naive-ui component library and is hosted and continuously deployed
 | 
				
			||||||
 | 
					      by Vercel. Third party opensource libraries are used in some tools, you may find the complete list in the
 | 
				
			||||||
      <n-button
 | 
					      <n-button
 | 
				
			||||||
        type="primary"
 | 
					        type="primary"
 | 
				
			||||||
        tag="a"
 | 
					        tag="a"
 | 
				
			||||||
@@ -47,12 +43,14 @@
 | 
				
			|||||||
        target="_blank"
 | 
					        target="_blank"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        package.json
 | 
					        package.json
 | 
				
			||||||
      </n-button> file of the repository.
 | 
					      </n-button>
 | 
				
			||||||
 | 
					      file of the repository.
 | 
				
			||||||
    </n-p>
 | 
					    </n-p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <n-h2>Found a bug ? A tool is missing ?</n-h2>
 | 
					    <n-h2>Found a bug ? A tool is missing ?</n-h2>
 | 
				
			||||||
    <n-p>
 | 
					    <n-p>
 | 
				
			||||||
      If you need a tool that is currently not present here, and you think can be relevant, you are welcome to submit a feature request in the 
 | 
					      If you need a tool that is currently not present here, and you think can be relevant, you are welcome to submit a
 | 
				
			||||||
 | 
					      feature request in the
 | 
				
			||||||
      <n-button
 | 
					      <n-button
 | 
				
			||||||
        type="primary"
 | 
					        type="primary"
 | 
				
			||||||
        tag="a"
 | 
					        tag="a"
 | 
				
			||||||
@@ -62,7 +60,8 @@
 | 
				
			|||||||
        target="_blank"
 | 
					        target="_blank"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        issues section
 | 
					        issues section
 | 
				
			||||||
      </n-button> in the github repository.
 | 
					      </n-button>
 | 
				
			||||||
 | 
					      in the github repository.
 | 
				
			||||||
    </n-p>
 | 
					    </n-p>
 | 
				
			||||||
    <n-p>
 | 
					    <n-p>
 | 
				
			||||||
      And if you found a bug, or something broken that doesn't work as expected, please fill a bug report in the
 | 
					      And if you found a bug, or something broken that doesn't work as expected, please fill a bug report in the
 | 
				
			||||||
@@ -75,12 +74,12 @@
 | 
				
			|||||||
        target="_blank"
 | 
					        target="_blank"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        issues section
 | 
					        issues section
 | 
				
			||||||
      </n-button> in the github repository.
 | 
					      </n-button>
 | 
				
			||||||
 | 
					      in the github repository.
 | 
				
			||||||
    </n-p>
 | 
					    </n-p>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped lang="less">
 | 
					<style scoped lang="less">
 | 
				
			||||||
.about-page {
 | 
					.about-page {
 | 
				
			||||||
  max-width: 600px;
 | 
					  max-width: 600px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,21 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { toolsWithCategory } from '@/tools';
 | 
					import { toolsWithCategory } from '@/tools';
 | 
				
			||||||
import ToolCard from '../components/ToolCard.vue';
 | 
					import ToolCard from '../components/ToolCard.vue';
 | 
				
			||||||
 | 
					import { useHead } from '@vueuse/head';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					useHead({ title: 'IT Tools - Handy online tools for developers' });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div class="home-page">
 | 
					  <div class="home-page">
 | 
				
			||||||
    <n-grid
 | 
					    <n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
 | 
				
			||||||
      x-gap="12"
 | 
					      <n-gi v-for="tool in toolsWithCategory.reverse().sort(({ isNew }) => (isNew ? -1 : 1))" :key="tool.name">
 | 
				
			||||||
      y-gap="12"
 | 
					 | 
				
			||||||
      cols="1 400:2 800:3 1200:4 2000:8"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-gi
 | 
					 | 
				
			||||||
        v-for="tool in toolsWithCategory"
 | 
					 | 
				
			||||||
        :key="tool.name"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <tool-card :tool="tool" />
 | 
					        <tool-card :tool="tool" />
 | 
				
			||||||
      </n-gi>
 | 
					      </n-gi>
 | 
				
			||||||
    </n-grid>
 | 
					    </n-grid>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
<style scoped lang="less">
 | 
					<style scoped lang="less">
 | 
				
			||||||
.home-page {
 | 
					.home-page {
 | 
				
			||||||
  padding-top: 50px;
 | 
					  padding-top: 50px;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,9 +50,11 @@ import {
 | 
				
			|||||||
  NImage,
 | 
					  NImage,
 | 
				
			||||||
  NScrollbar,
 | 
					  NScrollbar,
 | 
				
			||||||
  NGradientText,
 | 
					  NGradientText,
 | 
				
			||||||
 | 
					  NCode,
 | 
				
			||||||
} from 'naive-ui';
 | 
					} from 'naive-ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const components = [
 | 
					const components = [
 | 
				
			||||||
 | 
					  NCode,
 | 
				
			||||||
  NGradientText,
 | 
					  NGradientText,
 | 
				
			||||||
  NScrollbar,
 | 
					  NScrollbar,
 | 
				
			||||||
  NImage,
 | 
					  NImage,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,10 @@
 | 
				
			|||||||
import Plausible, { type PlausibleOptions } from 'plausible-tracker';
 | 
					import { config } from '@/config';
 | 
				
			||||||
 | 
					import Plausible from 'plausible-tracker';
 | 
				
			||||||
import type { App } from 'vue';
 | 
					import type { App } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const options: PlausibleOptions = {
 | 
					 | 
				
			||||||
  domain: import.meta.env.VITE_PLAUSIBLE_DOMAIN ,
 | 
					 | 
				
			||||||
  apiHost: import.meta.env.VITE_PLAUSIBLE_API_HOST,
 | 
					 | 
				
			||||||
  trackLocalhost: false,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const plausible = {
 | 
					export const plausible = {
 | 
				
			||||||
  install: (app: App) => {
 | 
					  install: (app: App) => {
 | 
				
			||||||
    const plausible = Plausible(options);
 | 
					    const plausible = Plausible(config.plausible);
 | 
				
			||||||
    plausible.enableAutoPageviews();
 | 
					    plausible.enableAutoPageviews();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.config.globalProperties.$plausible = plausible;
 | 
					    app.config.globalProperties.$plausible = plausible;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,14 +3,22 @@ import { createRouter, createWebHistory } from 'vue-router';
 | 
				
			|||||||
import HomePage from './pages/Home.page.vue';
 | 
					import HomePage from './pages/Home.page.vue';
 | 
				
			||||||
import NotFound from './pages/404.page.vue';
 | 
					import NotFound from './pages/404.page.vue';
 | 
				
			||||||
import { tools } from './tools';
 | 
					import { tools } from './tools';
 | 
				
			||||||
 | 
					import { config } from './config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({ path, name, component, meta: { isTool: true, layout: layouts.toolLayout, name, ...config } }));
 | 
					const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({
 | 
				
			||||||
 | 
					  path,
 | 
				
			||||||
 | 
					  name,
 | 
				
			||||||
 | 
					  component,
 | 
				
			||||||
 | 
					  meta: { isTool: true, layout: layouts.toolLayout, name, ...config },
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
const toolsRedirectRoutes = tools
 | 
					const toolsRedirectRoutes = tools
 | 
				
			||||||
  .filter(({ redirectFrom }) => redirectFrom && redirectFrom.length > 0)
 | 
					  .filter(({ redirectFrom }) => redirectFrom && redirectFrom.length > 0)
 | 
				
			||||||
  .flatMap(({ path, redirectFrom }) => redirectFrom?.map((redirectSource) => ({ path: redirectSource, redirect: path })) ?? []);
 | 
					  .flatMap(
 | 
				
			||||||
 | 
					    ({ path, redirectFrom }) => redirectFrom?.map((redirectSource) => ({ path: redirectSource, redirect: path })) ?? [],
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = createRouter({
 | 
					const router = createRouter({
 | 
				
			||||||
  history: createWebHistory(import.meta.env.BASE_URL),
 | 
					  history: createWebHistory(config.app.baseUrl),
 | 
				
			||||||
  routes: [
 | 
					  routes: [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      path: '/',
 | 
					      path: '/',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +1,14 @@
 | 
				
			|||||||
import { useMediaQuery, useStorage, whenever } from '@vueuse/core';
 | 
					import { useMediaQuery, useStorage } from '@vueuse/core';
 | 
				
			||||||
import { defineStore } from 'pinia';
 | 
					import { defineStore } from 'pinia';
 | 
				
			||||||
import type { Ref } from 'vue';
 | 
					import { watch, type Ref } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const useStyleStore = defineStore('style', {
 | 
					export const useStyleStore = defineStore('style', {
 | 
				
			||||||
  state: () => {
 | 
					  state: () => {
 | 
				
			||||||
    const isDarkTheme = useStorage('isDarkTheme', true) as Ref<boolean>;
 | 
					    const isDarkTheme = useStorage('isDarkTheme', true) as Ref<boolean>;
 | 
				
			||||||
    const isSmallScreen = useMediaQuery('(max-width: 700px)');
 | 
					    const isSmallScreen = useMediaQuery('(max-width: 700px)');
 | 
				
			||||||
    const isMenuCollapsed = useStorage('isMenuCollapsed', !isSmallScreen.value) as Ref<boolean>;
 | 
					    const isMenuCollapsed = useStorage('isMenuCollapsed', isSmallScreen.value) as Ref<boolean>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    whenever(
 | 
					    watch(isSmallScreen, (v) => (isMenuCollapsed.value = v));
 | 
				
			||||||
      () => !isSmallScreen.value,
 | 
					 | 
				
			||||||
      () => (isMenuCollapsed.value = false)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    whenever(
 | 
					 | 
				
			||||||
      () => isSmallScreen.value,
 | 
					 | 
				
			||||||
      () => (isMenuCollapsed.value = true)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      isDarkTheme,
 | 
					      isDarkTheme,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
import type { Component } from 'vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface ITool {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  path: string;
 | 
					 | 
				
			||||||
  description: string;
 | 
					 | 
				
			||||||
  keywords: string[];
 | 
					 | 
				
			||||||
  component: () => Promise<Component>;
 | 
					 | 
				
			||||||
  icon: Component;
 | 
					 | 
				
			||||||
  redirectFrom?: string[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface ToolCategory {
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  icon: Component;
 | 
					 | 
				
			||||||
  components: ITool[];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,92 +1,56 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card title="Text to base64">
 | 
					  <n-card title="Text to base64">
 | 
				
			||||||
    <n-input
 | 
					    <n-input v-model:value="textInput" type="textarea" placeholder="Put your string here..." />
 | 
				
			||||||
      v-model:value="textInput"
 | 
					    <n-input :value="textBase64" type="textarea" readonly />
 | 
				
			||||||
      type="textarea"
 | 
					 | 
				
			||||||
      placeholder="Put your string here..."
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <n-input
 | 
					 | 
				
			||||||
      :value="textBase64"
 | 
					 | 
				
			||||||
      type="textarea"
 | 
					 | 
				
			||||||
      readonly
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
      <n-button
 | 
					      <n-button secondary @click="copyTextBase64()"> Copy </n-button>
 | 
				
			||||||
        secondary
 | 
					 | 
				
			||||||
        @click="copyTextBase64()"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Copy
 | 
					 | 
				
			||||||
      </n-button>
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <n-card title="File to base64">
 | 
					  <n-card title="File to base64">
 | 
				
			||||||
    <n-upload
 | 
					    <n-upload v-model:file-list="fileList" :show-file-list="true" :on-before-upload="onUpload" list-type="image">
 | 
				
			||||||
      v-model:file-list="fileList"
 | 
					 | 
				
			||||||
      :show-file-list="true"
 | 
					 | 
				
			||||||
      :on-before-upload="onUpload"
 | 
					 | 
				
			||||||
      list-type="image"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-upload-dragger>
 | 
					      <n-upload-dragger>
 | 
				
			||||||
        <div style="margin-bottom: 12px">
 | 
					        <div style="margin-bottom: 12px">
 | 
				
			||||||
          <n-icon
 | 
					          <n-icon size="35" :depth="3" :component="Upload" />
 | 
				
			||||||
            size="35"
 | 
					 | 
				
			||||||
            :depth="3"
 | 
					 | 
				
			||||||
            :component="Upload"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <n-text style="font-size: 14px">
 | 
					        <n-text style="font-size: 14px"> Click or drag a file to this area to upload </n-text>
 | 
				
			||||||
          Click or drag a file to this area to upload
 | 
					 | 
				
			||||||
        </n-text>
 | 
					 | 
				
			||||||
      </n-upload-dragger>
 | 
					      </n-upload-dragger>
 | 
				
			||||||
    </n-upload>
 | 
					    </n-upload>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <n-input
 | 
					    <n-input :value="fileBase64" type="textarea" readonly />
 | 
				
			||||||
      :value="fileBase64"
 | 
					 | 
				
			||||||
      type="textarea"
 | 
					 | 
				
			||||||
      readonly
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
      <n-button
 | 
					      <n-button secondary @click="copyFileBase64()"> Copy </n-button>
 | 
				
			||||||
        secondary
 | 
					 | 
				
			||||||
        @click="copyFileBase64()"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Copy
 | 
					 | 
				
			||||||
      </n-button>
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy'
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { useBase64 } from '@vueuse/core'
 | 
					import { useBase64 } from '@vueuse/core';
 | 
				
			||||||
import { Upload } from '@vicons/tabler'
 | 
					import { Upload } from '@vicons/tabler';
 | 
				
			||||||
import { ref, type Ref } from 'vue'
 | 
					import { ref, type Ref } from 'vue';
 | 
				
			||||||
import type { UploadFileInfo } from 'naive-ui';
 | 
					import type { UploadFileInfo } from 'naive-ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const textInput = ref('')
 | 
					const textInput = ref('');
 | 
				
			||||||
const { base64: textBase64 } = useBase64(textInput)
 | 
					const { base64: textBase64 } = useBase64(textInput);
 | 
				
			||||||
const { copy: copyTextBase64 } = useCopy({ source: textBase64, text: 'Base64 string copied to the clipboard' })
 | 
					const { copy: copyTextBase64 } = useCopy({ source: textBase64, text: 'Base64 string copied to the clipboard' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const fileList = ref()
 | 
					const fileList = ref();
 | 
				
			||||||
const fileInput = ref() as Ref<File>
 | 
					const fileInput = ref() as Ref<File>;
 | 
				
			||||||
const { base64: fileBase64 } = useBase64(fileInput)
 | 
					const { base64: fileBase64 } = useBase64(fileInput);
 | 
				
			||||||
const { copy: copyFileBase64 } = useCopy({ source: fileBase64, text: 'Base64 string copied to the clipboard' })
 | 
					const { copy: copyFileBase64 } = useCopy({ source: fileBase64, text: 'Base64 string copied to the clipboard' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function onUpload({ file: { file } }: { file: UploadFileInfo }) {
 | 
				
			||||||
function onUpload({ file: { file }, }: { file: UploadFileInfo }) {
 | 
					 | 
				
			||||||
  if (file) {
 | 
					  if (file) {
 | 
				
			||||||
    fileList.value = []
 | 
					    fileList.value = [];
 | 
				
			||||||
    fileInput.value = file
 | 
					    fileInput.value = file;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
.n-input,
 | 
					.n-input,
 | 
				
			||||||
.n-upload,
 | 
					.n-upload {
 | 
				
			||||||
.n-card {
 | 
					 | 
				
			||||||
  margin-bottom: 15px;
 | 
					  margin-bottom: 15px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,12 @@
 | 
				
			|||||||
import { FileDigit } from '@vicons/tabler';
 | 
					import { FileDigit } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Base64 converter',
 | 
					  name: 'Base64 converter',
 | 
				
			||||||
  path: '/base64-converter',
 | 
					  path: '/base64-converter',
 | 
				
			||||||
  description: "Convert string, files or images into a it's base64 representation.",
 | 
					  description: "Convert string, files or images into a it's base64 representation.",
 | 
				
			||||||
  keywords: ['base64', 'converter', 'upload', 'image', 'file', 'convertion', 'web', 'data', 'format'],
 | 
					  keywords: ['base64', 'converter', 'upload', 'image', 'file', 'conversion', 'web', 'data', 'format'],
 | 
				
			||||||
  component: () => import('./base64-converter.vue'),
 | 
					  component: () => import('./base64-converter.vue'),
 | 
				
			||||||
  icon: FileDigit,
 | 
					  icon: FileDigit,
 | 
				
			||||||
  redirectFrom: ['/file-to-base64', '/base64-string-converter'],
 | 
					  redirectFrom: ['/file-to-base64', '/base64-string-converter'],
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card title="Hash">
 | 
					  <n-card title="Hash">
 | 
				
			||||||
    <n-form label-width="120">
 | 
					    <n-form label-width="120">
 | 
				
			||||||
      <n-form-item
 | 
					      <n-form-item label="Your string: " label-placement="left">
 | 
				
			||||||
        label="Your string: "
 | 
					 | 
				
			||||||
        label-placement="left"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-input
 | 
					        <n-input
 | 
				
			||||||
          v-model:value="input"
 | 
					          v-model:value="input"
 | 
				
			||||||
          placeholder="Your string to bcrypt..."
 | 
					          placeholder="Your string to bcrypt..."
 | 
				
			||||||
@@ -14,42 +11,20 @@
 | 
				
			|||||||
          spellcheck="false"
 | 
					          spellcheck="false"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item
 | 
					      <n-form-item label="Salt count: " label-placement="left">
 | 
				
			||||||
        label="Salt count: "
 | 
					        <n-input-number v-model:value="saltCount" placeholder="Salt rounds..." :max="10" :min="0" style="width: 100%" />
 | 
				
			||||||
        label-placement="left"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-input-number
 | 
					 | 
				
			||||||
          v-model:value="saltCount"
 | 
					 | 
				
			||||||
          placeholder="Salt rounds..."
 | 
					 | 
				
			||||||
          :max="10"
 | 
					 | 
				
			||||||
          :min="0"
 | 
					 | 
				
			||||||
          style="width: 100%;"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-input
 | 
					      <n-input :value="hashed" readonly style="text-align: center" />
 | 
				
			||||||
        :value="hashed"
 | 
					 | 
				
			||||||
        readonly
 | 
					 | 
				
			||||||
        style="text-align: center;"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </n-form>
 | 
					    </n-form>
 | 
				
			||||||
    <br>
 | 
					    <br />
 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
      <n-button
 | 
					      <n-button secondary @click="copy"> Copy hash </n-button>
 | 
				
			||||||
        secondary
 | 
					 | 
				
			||||||
        @click="copy"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Copy hash
 | 
					 | 
				
			||||||
      </n-button>
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <br>
 | 
					 | 
				
			||||||
  <n-card title="Compare string with hash">
 | 
					  <n-card title="Compare string with hash">
 | 
				
			||||||
    <n-form label-width="120">
 | 
					    <n-form label-width="120">
 | 
				
			||||||
      <n-form-item
 | 
					      <n-form-item label="Your string: " label-placement="left">
 | 
				
			||||||
        label="Your string: "
 | 
					 | 
				
			||||||
        label-placement="left"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-input
 | 
					        <n-input
 | 
				
			||||||
          v-model:value="compareString"
 | 
					          v-model:value="compareString"
 | 
				
			||||||
          placeholder="Your string to compare..."
 | 
					          placeholder="Your string to compare..."
 | 
				
			||||||
@@ -59,10 +34,7 @@
 | 
				
			|||||||
          spellcheck="false"
 | 
					          spellcheck="false"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item
 | 
					      <n-form-item label="Your hash: " label-placement="left">
 | 
				
			||||||
        label="Your hash: "
 | 
					 | 
				
			||||||
        label-placement="left"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-input
 | 
					        <n-input
 | 
				
			||||||
          v-model:value="compareHash"
 | 
					          v-model:value="compareHash"
 | 
				
			||||||
          placeholder="Your hahs to compare..."
 | 
					          placeholder="Your hahs to compare..."
 | 
				
			||||||
@@ -72,15 +44,8 @@
 | 
				
			|||||||
          spellcheck="false"
 | 
					          spellcheck="false"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item
 | 
					      <n-form-item label="Do they match ? " label-placement="left" :show-feedback="false">
 | 
				
			||||||
        label="Do they match ? "
 | 
					        <div class="compare-result" :class="{ positive: compareMatch }">
 | 
				
			||||||
        label-placement="left"
 | 
					 | 
				
			||||||
        :show-feedback="false"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <div
 | 
					 | 
				
			||||||
          class="compare-result"
 | 
					 | 
				
			||||||
          :class="{positive:compareMatch}"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          {{ compareMatch ? 'Yes' : 'No' }}
 | 
					          {{ compareMatch ? 'Yes' : 'No' }}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
@@ -90,21 +55,20 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { computed, ref } from 'vue';
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
import {hashSync, compareSync} from 'bcryptjs'
 | 
					import { hashSync, compareSync } from 'bcryptjs';
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { useThemeVars } from 'naive-ui';
 | 
					import { useThemeVars } from 'naive-ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const themeVars = useThemeVars()
 | 
					const themeVars = useThemeVars();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const input = ref('')
 | 
					const input = ref('');
 | 
				
			||||||
const saltCount = ref(10)
 | 
					const saltCount = ref(10);
 | 
				
			||||||
const hashed = computed(() => hashSync(input.value, saltCount.value))
 | 
					const hashed = computed(() => hashSync(input.value, saltCount.value));
 | 
				
			||||||
const {copy} = useCopy({source: hashed, text:'Hashed string copied to the clipboard'})
 | 
					const { copy } = useCopy({ source: hashed, text: 'Hashed string copied to the clipboard' });
 | 
				
			||||||
 | 
					 | 
				
			||||||
const compareString = ref('')
 | 
					 | 
				
			||||||
const compareHash = ref('')
 | 
					 | 
				
			||||||
const compareMatch = computed(() => compareSync(compareString.value, compareHash.value))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const compareString = ref('');
 | 
				
			||||||
 | 
					const compareHash = ref('');
 | 
				
			||||||
 | 
					const compareMatch = computed(() => compareSync(compareString.value, compareHash.value));
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
@@ -112,7 +76,7 @@ const compareMatch = computed(() => compareSync(compareString.value, compareHash
 | 
				
			|||||||
  color: v-bind('themeVars.errorColor');
 | 
					  color: v-bind('themeVars.errorColor');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &.positive {
 | 
					  &.positive {
 | 
				
			||||||
    color: v-bind('themeVars.successColor')
 | 
					    color: v-bind('themeVars.successColor');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
import { LockSquare } from '@vicons/tabler';
 | 
					import { LockSquare } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Bcrypt',
 | 
					  name: 'Bcrypt',
 | 
				
			||||||
  path: '/bcrypt',
 | 
					  path: '/bcrypt',
 | 
				
			||||||
  description: 'Hash and compare text string using bcrypt. Bcrypt is a password-hashing function based on the Blowfish cipher.',
 | 
					  description:
 | 
				
			||||||
 | 
					    'Hash and compare text string using bcrypt. Bcrypt is a password-hashing function based on the Blowfish cipher.',
 | 
				
			||||||
  keywords: ['bcrypt', 'hash', 'compare', 'password', 'salt', 'round', 'storage', 'crypto'],
 | 
					  keywords: ['bcrypt', 'hash', 'compare', 'password', 'salt', 'round', 'storage', 'crypto'],
 | 
				
			||||||
  component: () => import('./bcrypt.vue'),
 | 
					  component: () => import('./bcrypt.vue'),
 | 
				
			||||||
  icon: LockSquare,
 | 
					  icon: LockSquare,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,12 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <n-card>
 | 
					    <n-card>
 | 
				
			||||||
      <n-grid
 | 
					      <n-grid cols="3" x-gap="12">
 | 
				
			||||||
        cols="3"
 | 
					 | 
				
			||||||
        x-gap="12"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-gi span="1">
 | 
					        <n-gi span="1">
 | 
				
			||||||
          <n-form-item label="Language:">
 | 
					          <n-form-item label="Language:">
 | 
				
			||||||
            <n-select
 | 
					            <n-select
 | 
				
			||||||
              v-model:value="language"
 | 
					              v-model:value="language"
 | 
				
			||||||
              :options="Object.keys(languages).map(label => ({ label, value: label }))"
 | 
					              :options="Object.keys(languages).map((label) => ({ label, value: label }))"
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          </n-form-item>
 | 
					          </n-form-item>
 | 
				
			||||||
        </n-gi>
 | 
					        </n-gi>
 | 
				
			||||||
@@ -20,10 +17,7 @@
 | 
				
			|||||||
            :validation-status="entropyValidation.status"
 | 
					            :validation-status="entropyValidation.status"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <n-input-group>
 | 
					            <n-input-group>
 | 
				
			||||||
              <n-input
 | 
					              <n-input v-model:value="entropy" placeholder="Your string..." />
 | 
				
			||||||
                v-model:value="entropy"
 | 
					 | 
				
			||||||
                placeholder="Your string..."
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
              <n-button @click="refreshEntropy">
 | 
					              <n-button @click="refreshEntropy">
 | 
				
			||||||
                <n-icon size="22">
 | 
					                <n-icon size="22">
 | 
				
			||||||
                  <Refresh />
 | 
					                  <Refresh />
 | 
				
			||||||
@@ -46,7 +40,7 @@
 | 
				
			|||||||
        <n-input-group>
 | 
					        <n-input-group>
 | 
				
			||||||
          <n-input
 | 
					          <n-input
 | 
				
			||||||
            v-model:value="passphrase"
 | 
					            v-model:value="passphrase"
 | 
				
			||||||
            style="text-align: center; flex: 1;"
 | 
					            style="text-align: center; flex: 1"
 | 
				
			||||||
            placeholder="Your mnemonic..."
 | 
					            placeholder="Your mnemonic..."
 | 
				
			||||||
            autocomplete="off"
 | 
					            autocomplete="off"
 | 
				
			||||||
            autocorrect="off"
 | 
					            autocorrect="off"
 | 
				
			||||||
@@ -55,10 +49,7 @@
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <n-button @click="copyPassphrase">
 | 
					          <n-button @click="copyPassphrase">
 | 
				
			||||||
            <n-icon
 | 
					            <n-icon size="22" :component="Copy" />
 | 
				
			||||||
              size="22"
 | 
					 | 
				
			||||||
              :component="Copy"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </n-button>
 | 
					          </n-button>
 | 
				
			||||||
        </n-input-group>
 | 
					        </n-input-group>
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
@@ -68,7 +59,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { ref, computed } from 'vue'
 | 
					import { ref, computed } from 'vue';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  entropyToMnemonic,
 | 
					  entropyToMnemonic,
 | 
				
			||||||
  englishWordList,
 | 
					  englishWordList,
 | 
				
			||||||
@@ -82,61 +73,60 @@ import {
 | 
				
			|||||||
  portugueseWordList,
 | 
					  portugueseWordList,
 | 
				
			||||||
  spanishWordList,
 | 
					  spanishWordList,
 | 
				
			||||||
  generateEntropy,
 | 
					  generateEntropy,
 | 
				
			||||||
    mnemonicToEntropy
 | 
					  mnemonicToEntropy,
 | 
				
			||||||
} from '@it-tools/bip39'
 | 
					} from '@it-tools/bip39';
 | 
				
			||||||
import { Copy, Refresh } from '@vicons/tabler'
 | 
					import { Copy, Refresh } from '@vicons/tabler';
 | 
				
			||||||
import { useValidation } from '@/composable/validation';
 | 
					import { useValidation } from '@/composable/validation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
const languages = {
 | 
					const languages = {
 | 
				
			||||||
    'English': englishWordList,
 | 
					  English: englishWordList,
 | 
				
			||||||
  'Chinese simplified': chineseSimplifiedWordList,
 | 
					  'Chinese simplified': chineseSimplifiedWordList,
 | 
				
			||||||
  'Chinese traditional': chineseTraditionalWordList,
 | 
					  'Chinese traditional': chineseTraditionalWordList,
 | 
				
			||||||
    'Czech': czechWordList,
 | 
					  Czech: czechWordList,
 | 
				
			||||||
    'French': frenchWordList,
 | 
					  French: frenchWordList,
 | 
				
			||||||
    'Italian': italianWordList,
 | 
					  Italian: italianWordList,
 | 
				
			||||||
    'Japanese': japaneseWordList,
 | 
					  Japanese: japaneseWordList,
 | 
				
			||||||
    'Korean': koreanWordList,
 | 
					  Korean: koreanWordList,
 | 
				
			||||||
    'Portuguese': portugueseWordList,
 | 
					  Portuguese: portugueseWordList,
 | 
				
			||||||
    'Spanish': spanishWordList
 | 
					  Spanish: spanishWordList,
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const entropy = ref(generateEntropy())
 | 
					const entropy = ref(generateEntropy());
 | 
				
			||||||
const passphraseInput = ref('')
 | 
					const passphraseInput = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const language = ref<keyof typeof languages>('English')
 | 
					const language = ref<keyof typeof languages>('English');
 | 
				
			||||||
const passphrase = computed({
 | 
					const passphrase = computed({
 | 
				
			||||||
  get() {
 | 
					  get() {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
            return entropyToMnemonic(entropy.value, languages[language.value])
 | 
					      return entropyToMnemonic(entropy.value, languages[language.value]);
 | 
				
			||||||
    } catch (_) {
 | 
					    } catch (_) {
 | 
				
			||||||
            return passphraseInput.value
 | 
					      return passphraseInput.value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  set(value: string) {
 | 
					  set(value: string) {
 | 
				
			||||||
        passphraseInput.value = value
 | 
					    passphraseInput.value = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
            entropy.value = mnemonicToEntropy(value, languages[language.value])
 | 
					      entropy.value = mnemonicToEntropy(value, languages[language.value]);
 | 
				
			||||||
    } catch (_) {
 | 
					    } catch (_) {
 | 
				
			||||||
            entropy.value = ''
 | 
					      entropy.value = '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    }
 | 
					  },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const entropyValidation = useValidation({
 | 
					const entropyValidation = useValidation({
 | 
				
			||||||
  source: entropy,
 | 
					  source: entropy,
 | 
				
			||||||
  rules: [
 | 
					  rules: [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      validator: (value) => value === '' || (value.length <= 32 && value.length >= 16 && value.length % 4 === 0),
 | 
					      validator: (value) => value === '' || (value.length <= 32 && value.length >= 16 && value.length % 4 === 0),
 | 
				
			||||||
            message: 'Entropy length should be >= 16, <= 32 and be a multiple of 4'
 | 
					      message: 'Entropy length should be >= 16, <= 32 and be a multiple of 4',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      validator: (value) => /^[a-fA-F0-9]*$/.test(value),
 | 
					      validator: (value) => /^[a-fA-F0-9]*$/.test(value),
 | 
				
			||||||
            message: 'Entropy should an hexadecimal number'
 | 
					      message: 'Entropy should an hexadecimal number',
 | 
				
			||||||
        }
 | 
					    },
 | 
				
			||||||
    ]
 | 
					  ],
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const mnemonicValidation = useValidation({
 | 
					const mnemonicValidation = useValidation({
 | 
				
			||||||
  source: passphrase,
 | 
					  source: passphrase,
 | 
				
			||||||
@@ -144,23 +134,21 @@ const mnemonicValidation = useValidation({
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
      validator: (value) => {
 | 
					      validator: (value) => {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
                    mnemonicToEntropy(value)
 | 
					          mnemonicToEntropy(value);
 | 
				
			||||||
                    return true
 | 
					          return true;
 | 
				
			||||||
        } catch (_) {
 | 
					        } catch (_) {
 | 
				
			||||||
                    return false
 | 
					          return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
            message: 'Invalid mnemonic'
 | 
					      message: 'Invalid mnemonic',
 | 
				
			||||||
        }
 | 
					    },
 | 
				
			||||||
    ]
 | 
					  ],
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function refreshEntropy() {
 | 
					function refreshEntropy() {
 | 
				
			||||||
    entropy.value = generateEntropy()
 | 
					  entropy.value = generateEntropy();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { copy: copyEntropy } = useCopy({ source: entropy, text: 'Entropy copied to the clipboard' })
 | 
					const { copy: copyEntropy } = useCopy({ source: entropy, text: 'Entropy copied to the clipboard' });
 | 
				
			||||||
const { copy: copyPassphrase } = useCopy({ source: passphrase, text: 'Passphrase copied to the clipboard' })
 | 
					const { copy: copyPassphrase } = useCopy({ source: passphrase, text: 'Passphrase copied to the clipboard' });
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { AlignJustified } from '@vicons/tabler';
 | 
					import { AlignJustified } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'BIP39 passphrase generator',
 | 
					  name: 'BIP39 passphrase generator',
 | 
				
			||||||
  path: '/bip39-generator',
 | 
					  path: '/bip39-generator',
 | 
				
			||||||
  description: 'Generate BIP39 passphrase from existing or random mnemonic, or get the mnemonic from the passphrase.',
 | 
					  description: 'Generate BIP39 passphrase from existing or random mnemonic, or get the mnemonic from the passphrase.',
 | 
				
			||||||
  keywords: ['BIP39', 'passphrase', 'generator', 'mnemonic', 'entropy'],
 | 
					  keywords: ['BIP39', 'passphrase', 'generator', 'mnemonic', 'entropy'],
 | 
				
			||||||
  component: () => import('./bip39-generator.vue'),
 | 
					  component: () => import('./bip39-generator.vue'),
 | 
				
			||||||
  icon: AlignJustified,
 | 
					  icon: AlignJustified,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card>
 | 
					  <n-card>
 | 
				
			||||||
    <n-form
 | 
					    <n-form label-width="120" label-placement="left" :show-feedback="false">
 | 
				
			||||||
      label-width="120"
 | 
					 | 
				
			||||||
      label-placement="left"
 | 
					 | 
				
			||||||
      :show-feedback="false"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-form-item label="Your string:">
 | 
					      <n-form-item label="Your string:">
 | 
				
			||||||
        <n-input v-model:value="input" />
 | 
					        <n-input v-model:value="input" />
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
@@ -50,7 +46,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from 'vue';
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import InputCopyable from "../../components/InputCopyable.vue";
 | 
					import InputCopyable from '../../components/InputCopyable.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  camelCase,
 | 
					  camelCase,
 | 
				
			||||||
@@ -64,10 +60,9 @@ import {
 | 
				
			|||||||
  pathCase,
 | 
					  pathCase,
 | 
				
			||||||
  sentenceCase,
 | 
					  sentenceCase,
 | 
				
			||||||
  snakeCase,
 | 
					  snakeCase,
 | 
				
			||||||
} from "change-case";
 | 
					} from 'change-case';
 | 
				
			||||||
 | 
					 | 
				
			||||||
const input = ref('lorem ipsum dolor sit amet')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const input = ref('lorem ipsum dolor sit amet');
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,25 @@
 | 
				
			|||||||
import { LetterCaseToggle } from '@vicons/tabler';
 | 
					import { LetterCaseToggle } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Case converter',
 | 
					  name: 'Case converter',
 | 
				
			||||||
  path: '/case-converter',
 | 
					  path: '/case-converter',
 | 
				
			||||||
  description: 'Change the case of a string and chose between different formats',
 | 
					  description: 'Change the case of a string and chose between different formats',
 | 
				
			||||||
  keywords: ['case', 'converter', 'camelCase', 'capitalCase', 'constantCase', 'dotCase', 'headerCase', 'noCase', 'paramCase', 'pascalCase', 'pathCase', 'sentenceCase', 'snakeCase', ],
 | 
					  keywords: [
 | 
				
			||||||
 | 
					    'case',
 | 
				
			||||||
 | 
					    'converter',
 | 
				
			||||||
 | 
					    'camelCase',
 | 
				
			||||||
 | 
					    'capitalCase',
 | 
				
			||||||
 | 
					    'constantCase',
 | 
				
			||||||
 | 
					    'dotCase',
 | 
				
			||||||
 | 
					    'headerCase',
 | 
				
			||||||
 | 
					    'noCase',
 | 
				
			||||||
 | 
					    'paramCase',
 | 
				
			||||||
 | 
					    'pascalCase',
 | 
				
			||||||
 | 
					    'pathCase',
 | 
				
			||||||
 | 
					    'sentenceCase',
 | 
				
			||||||
 | 
					    'snakeCase',
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  component: () => import('./case-converter.vue'),
 | 
					  component: () => import('./case-converter.vue'),
 | 
				
			||||||
  icon: LetterCaseToggle,
 | 
					  icon: LetterCaseToggle,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,6 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card>
 | 
					  <n-card>
 | 
				
			||||||
    <n-form
 | 
					    <n-form label-width="100" label-placement="left">
 | 
				
			||||||
      label-width="100"
 | 
					 | 
				
			||||||
      label-placement="left"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-form-item label="color picker:">
 | 
					      <n-form-item label="color picker:">
 | 
				
			||||||
        <n-color-picker
 | 
					        <n-color-picker
 | 
				
			||||||
          v-model:value="hex"
 | 
					          v-model:value="hex"
 | 
				
			||||||
@@ -12,46 +9,25 @@
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item label="color name:">
 | 
					      <n-form-item label="color name:">
 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable v-model:value="name" :on-input="(v: string) => onInputUpdated(v, 'name')" />
 | 
				
			||||||
          v-model:value="name"
 | 
					 | 
				
			||||||
          :on-input="(v: string) => onInputUpdated(v, 'name')"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item label="hex:">
 | 
					      <n-form-item label="hex:">
 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable v-model:value="hex" :on-input="(v: string) => onInputUpdated(v, 'hex')" />
 | 
				
			||||||
          v-model:value="hex"
 | 
					 | 
				
			||||||
          :on-input="(v: string) => onInputUpdated(v, 'hex')"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item label="rgb:">
 | 
					      <n-form-item label="rgb:">
 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable v-model:value="rgb" :on-input="(v: string) => onInputUpdated(v, 'rgb')" />
 | 
				
			||||||
          v-model:value="rgb"
 | 
					 | 
				
			||||||
          :on-input="(v: string) => onInputUpdated(v, 'rgb')"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item label="hsl:">
 | 
					      <n-form-item label="hsl:">
 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable v-model:value="hsl" :on-input="(v: string) => onInputUpdated(v, 'hsl')" />
 | 
				
			||||||
          v-model:value="hsl"
 | 
					 | 
				
			||||||
          :on-input="(v: string) => onInputUpdated(v, 'hsl')"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item label="hwb:">
 | 
					      <n-form-item label="hwb:">
 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable v-model:value="hwb" :on-input="(v: string) => onInputUpdated(v, 'hwb')" />
 | 
				
			||||||
          v-model:value="hwb"
 | 
					 | 
				
			||||||
          :on-input="(v: string) => onInputUpdated(v, 'hwb')"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item label="lch:">
 | 
					      <n-form-item label="lch:">
 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable v-model:value="lch" :on-input="(v: string) => onInputUpdated(v, 'lch')" />
 | 
				
			||||||
          v-model:value="lch"
 | 
					 | 
				
			||||||
          :on-input="(v: string) => onInputUpdated(v, 'lch')"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-form-item label="cmyk:">
 | 
					      <n-form-item label="cmyk:">
 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable v-model:value="cmyk" :on-input="(v: string) => onInputUpdated(v, 'cmyk')" />
 | 
				
			||||||
          v-model:value="cmyk"
 | 
					 | 
				
			||||||
          :on-input="(v: string) => onInputUpdated(v, 'cmyk')"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
    </n-form>
 | 
					    </n-form>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
@@ -59,23 +35,23 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from 'vue';
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import { colord, extend } from "colord";
 | 
					import { colord, extend } from 'colord';
 | 
				
			||||||
import InputCopyable from "../../components/InputCopyable.vue";
 | 
					import InputCopyable from '../../components/InputCopyable.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cmykPlugin from "colord/plugins/cmyk";
 | 
					import cmykPlugin from 'colord/plugins/cmyk';
 | 
				
			||||||
import hwbPlugin from "colord/plugins/hwb";
 | 
					import hwbPlugin from 'colord/plugins/hwb';
 | 
				
			||||||
import namesPlugin from "colord/plugins/names";
 | 
					import namesPlugin from 'colord/plugins/names';
 | 
				
			||||||
import lchPlugin from "colord/plugins/lch";
 | 
					import lchPlugin from 'colord/plugins/lch';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extend([cmykPlugin, hwbPlugin, namesPlugin, lchPlugin]);
 | 
					extend([cmykPlugin, hwbPlugin, namesPlugin, lchPlugin]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const name = ref('')
 | 
					const name = ref('');
 | 
				
			||||||
const hex = ref('#1ea54cff')
 | 
					const hex = ref('#1ea54cff');
 | 
				
			||||||
const rgb = ref('')
 | 
					const rgb = ref('');
 | 
				
			||||||
const hsl = ref('')
 | 
					const hsl = ref('');
 | 
				
			||||||
const hwb = ref('')
 | 
					const hwb = ref('');
 | 
				
			||||||
const cmyk = ref('')
 | 
					const cmyk = ref('');
 | 
				
			||||||
const lch = ref('')
 | 
					const lch = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onInputUpdated(value: string, omit: string) {
 | 
					function onInputUpdated(value: string, omit: string) {
 | 
				
			||||||
  const color = colord(value);
 | 
					  const color = colord(value);
 | 
				
			||||||
@@ -89,6 +65,5 @@ function onInputUpdated(value: string, omit: string) {
 | 
				
			|||||||
  if (omit !== 'lch') lch.value = color.toLchString();
 | 
					  if (omit !== 'lch') lch.value = color.toLchString();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onInputUpdated(hex.value, 'hex')
 | 
					onInputUpdated(hex.value, 'hex');
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Palette } from '@vicons/tabler';
 | 
					import { Palette } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Color converter',
 | 
					  name: 'Color converter',
 | 
				
			||||||
  path: '/color-converter',
 | 
					  path: '/color-converter',
 | 
				
			||||||
  description: 'Convert color between the different formats (hex, rgb, hsl and css name)',
 | 
					  description: 'Convert color between the different formats (hex, rgb, hsl and css name)',
 | 
				
			||||||
@@ -9,4 +9,4 @@ export const tool: ITool = {
 | 
				
			|||||||
  component: () => import('./color-converter.vue'),
 | 
					  component: () => import('./color-converter.vue'),
 | 
				
			||||||
  icon: Palette,
 | 
					  icon: Palette,
 | 
				
			||||||
  redirectFrom: ['/color-picker-converter'],
 | 
					  redirectFrom: ['/color-picker-converter'],
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,11 +6,7 @@
 | 
				
			|||||||
      :feedback="cronValidation.message"
 | 
					      :feedback="cronValidation.message"
 | 
				
			||||||
      :validation-status="cronValidation.status"
 | 
					      :validation-status="cronValidation.status"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <n-input
 | 
					      <n-input v-model:value="cron" size="large" placeholder="* * * * *" />
 | 
				
			||||||
        v-model:value="cron"
 | 
					 | 
				
			||||||
        size="large"
 | 
					 | 
				
			||||||
        placeholder="* * * * *"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
    <div class="cron-string">
 | 
					    <div class="cron-string">
 | 
				
			||||||
      {{ cronString }}
 | 
					      {{ cronString }}
 | 
				
			||||||
@@ -19,11 +15,7 @@
 | 
				
			|||||||
    <n-divider />
 | 
					    <n-divider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
      <n-form
 | 
					      <n-form :show-feedback="false" label-width="170" label-placement="left">
 | 
				
			||||||
        :show-feedback="false"
 | 
					 | 
				
			||||||
        label-width="170"
 | 
					 | 
				
			||||||
        label-placement="left"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-form-item label="Verbose">
 | 
					        <n-form-item label="Verbose">
 | 
				
			||||||
          <n-switch v-model:value="cronstrueConfig.verbose" />
 | 
					          <n-switch v-model:value="cronstrueConfig.verbose" />
 | 
				
			||||||
        </n-form-item>
 | 
					        </n-form-item>
 | 
				
			||||||
@@ -36,7 +28,6 @@
 | 
				
			|||||||
      </n-form>
 | 
					      </n-form>
 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
  <br>
 | 
					 | 
				
			||||||
  <n-card>
 | 
					  <n-card>
 | 
				
			||||||
    <pre>
 | 
					    <pre>
 | 
				
			||||||
┌──────────── [optional] seconds (0 - 59)
 | 
					┌──────────── [optional] seconds (0 - 59)
 | 
				
			||||||
@@ -46,62 +37,39 @@
 | 
				
			|||||||
| | | | ┌──── month (1 - 12) OR jan,feb,mar,apr ...
 | 
					| | | | ┌──── month (1 - 12) OR jan,feb,mar,apr ...
 | 
				
			||||||
| | | | | ┌── day of week (0 - 6, sunday=0) OR sun,mon ...
 | 
					| | | | | ┌── day of week (0 - 6, sunday=0) OR sun,mon ...
 | 
				
			||||||
| | | | | |
 | 
					| | | | | |
 | 
				
			||||||
* * * * * * command</pre>
 | 
					* * * * * * command</pre
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <n-space
 | 
					 | 
				
			||||||
      v-if="styleStore.isSmallScreen"
 | 
					 | 
				
			||||||
      vertical
 | 
					 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <n-card
 | 
					
 | 
				
			||||||
        v-for="{symbol, meaning, example, equivalent} in helpers"
 | 
					    <n-space v-if="styleStore.isSmallScreen" vertical>
 | 
				
			||||||
        :key="symbol"
 | 
					      <n-card v-for="{ symbol, meaning, example, equivalent } in helpers" :key="symbol" embedded :bordered="false">
 | 
				
			||||||
        embedded
 | 
					        <div>
 | 
				
			||||||
        :bordered="false"
 | 
					          Symbol: <strong>{{ symbol }}</strong>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          Meaning: <strong>{{ meaning }}</strong>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div>
 | 
				
			||||||
 | 
					          Example:
 | 
				
			||||||
 | 
					          <strong
 | 
				
			||||||
 | 
					            ><code>{{ example }}</code></strong
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
        <div>Symbol: <strong>{{ symbol }}</strong></div>
 | 
					        </div>
 | 
				
			||||||
        <div>Meaning: <strong>{{ meaning }}</strong></div>
 | 
					        <div>
 | 
				
			||||||
        <div>Example: <strong><code>{{ example }}</code></strong></div>
 | 
					          Equivalent: <strong>{{ equivalent }}</strong>
 | 
				
			||||||
        <div>Equivalent: <strong>{{ equivalent }}</strong></div>
 | 
					        </div>
 | 
				
			||||||
      </n-card>
 | 
					      </n-card>
 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
    <n-table
 | 
					    <n-table v-else size="small">
 | 
				
			||||||
      v-else
 | 
					 | 
				
			||||||
      size="small"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <thead>
 | 
					      <thead>
 | 
				
			||||||
        <tr>
 | 
					        <tr>
 | 
				
			||||||
          <th
 | 
					          <th class="text-left" scope="col">Symbol</th>
 | 
				
			||||||
            class="text-left"
 | 
					          <th class="text-left" scope="col">Meaning</th>
 | 
				
			||||||
            scope="col"
 | 
					          <th class="text-left" scope="col">Example</th>
 | 
				
			||||||
          >
 | 
					          <th class="text-left" scope="col">Equivalent</th>
 | 
				
			||||||
            Symbol
 | 
					 | 
				
			||||||
          </th>
 | 
					 | 
				
			||||||
          <th
 | 
					 | 
				
			||||||
            class="text-left"
 | 
					 | 
				
			||||||
            scope="col"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            Meaning
 | 
					 | 
				
			||||||
          </th>
 | 
					 | 
				
			||||||
          <th
 | 
					 | 
				
			||||||
            class="text-left"
 | 
					 | 
				
			||||||
            scope="col"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            Example
 | 
					 | 
				
			||||||
          </th>
 | 
					 | 
				
			||||||
          <th
 | 
					 | 
				
			||||||
            class="text-left"
 | 
					 | 
				
			||||||
            scope="col"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            Equivalent
 | 
					 | 
				
			||||||
          </th>
 | 
					 | 
				
			||||||
        </tr>
 | 
					        </tr>
 | 
				
			||||||
      </thead>
 | 
					      </thead>
 | 
				
			||||||
      <tbody>
 | 
					      <tbody>
 | 
				
			||||||
        <tr
 | 
					        <tr v-for="{ symbol, meaning, example, equivalent } in helpers" :key="symbol">
 | 
				
			||||||
          v-for="{symbol, meaning, example, equivalent} in helpers"
 | 
					 | 
				
			||||||
          :key="symbol"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <td>{{ symbol }}</td>
 | 
					          <td>{{ symbol }}</td>
 | 
				
			||||||
          <td>{{ meaning }}</td>
 | 
					          <td>{{ meaning }}</td>
 | 
				
			||||||
          <td>
 | 
					          <td>
 | 
				
			||||||
@@ -115,116 +83,117 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import cronstrue from 'cronstrue'
 | 
					import cronstrue from 'cronstrue';
 | 
				
			||||||
import { isValidCron } from 'cron-validator'
 | 
					import { isValidCron } from 'cron-validator';
 | 
				
			||||||
import { computed, reactive, ref } from 'vue';
 | 
					import { computed, reactive, ref } from 'vue';
 | 
				
			||||||
import { useValidation } from '@/composable/validation';
 | 
					import { useValidation } from '@/composable/validation';
 | 
				
			||||||
import { useStyleStore } from '@/stores/style.store';
 | 
					import { useStyleStore } from '@/stores/style.store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
function isCronValid(v: string) {
 | 
					function isCronValid(v: string) {
 | 
				
			||||||
  return isValidCron(v, { allowBlankDay: true, alias: true, seconds: true })
 | 
					  return isValidCron(v, { allowBlankDay: true, alias: true, seconds: true });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const styleStore = useStyleStore()
 | 
					const styleStore = useStyleStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cron = ref('40 * * * *')
 | 
					const cron = ref('40 * * * *');
 | 
				
			||||||
const cronstrueConfig = reactive({
 | 
					const cronstrueConfig = reactive({
 | 
				
			||||||
  verbose: true,
 | 
					  verbose: true,
 | 
				
			||||||
  dayOfWeekStartIndexZero: true,
 | 
					  dayOfWeekStartIndexZero: true,
 | 
				
			||||||
  use24HourTimeFormat: true,
 | 
					  use24HourTimeFormat: true,
 | 
				
			||||||
  throwExceptionOnParseError: true
 | 
					  throwExceptionOnParseError: true,
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const helpers = [
 | 
					const helpers = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "*",
 | 
					    symbol: '*',
 | 
				
			||||||
        "meaning": "Any value",
 | 
					    meaning: 'Any value',
 | 
				
			||||||
        "example": "* * * *",
 | 
					    example: '* * * *',
 | 
				
			||||||
        "equivalent": "Every minute"
 | 
					    equivalent: 'Every minute',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "-",
 | 
					    symbol: '-',
 | 
				
			||||||
        "meaning": "Range of values",
 | 
					    meaning: 'Range of values',
 | 
				
			||||||
        "example": "1-10 * * *",
 | 
					    example: '1-10 * * *',
 | 
				
			||||||
        "equivalent": "Minutes 1 through 10"
 | 
					    equivalent: 'Minutes 1 through 10',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": ",",
 | 
					    symbol: ',',
 | 
				
			||||||
        "meaning": "List of values",
 | 
					    meaning: 'List of values',
 | 
				
			||||||
        "example": "1,10 * * *",
 | 
					    example: '1,10 * * *',
 | 
				
			||||||
        "equivalent": "At minutes 1 and 10"
 | 
					    equivalent: 'At minutes 1 and 10',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "/",
 | 
					    symbol: '/',
 | 
				
			||||||
        "meaning": "Step values",
 | 
					    meaning: 'Step values',
 | 
				
			||||||
        "example": "*/10 * * *",
 | 
					    example: '*/10 * * *',
 | 
				
			||||||
        "equivalent": "Every 10 minutes"
 | 
					    equivalent: 'Every 10 minutes',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@yearly",
 | 
					    symbol: '@yearly',
 | 
				
			||||||
        "meaning": "Once every year at midnight of 1 January",
 | 
					    meaning: 'Once every year at midnight of 1 January',
 | 
				
			||||||
        "example": "@yearly",
 | 
					    example: '@yearly',
 | 
				
			||||||
        "equivalent": "0 0 1 1 *"
 | 
					    equivalent: '0 0 1 1 *',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@annually",
 | 
					    symbol: '@annually',
 | 
				
			||||||
        "meaning": "Same as @yearly",
 | 
					    meaning: 'Same as @yearly',
 | 
				
			||||||
        "example": "@annually",
 | 
					    example: '@annually',
 | 
				
			||||||
        "equivalent": "0 0 1 1 *"
 | 
					    equivalent: '0 0 1 1 *',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@monthly",
 | 
					    symbol: '@monthly',
 | 
				
			||||||
        "meaning": "Once a month at midnight on the first day",
 | 
					    meaning: 'Once a month at midnight on the first day',
 | 
				
			||||||
        "example": "@monthly",
 | 
					    example: '@monthly',
 | 
				
			||||||
        "equivalent": "0 0 1 * *"
 | 
					    equivalent: '0 0 1 * *',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@weekly",
 | 
					    symbol: '@weekly',
 | 
				
			||||||
        "meaning": "Once a week at midnight on Sunday morning",
 | 
					    meaning: 'Once a week at midnight on Sunday morning',
 | 
				
			||||||
        "example": "@weekly",
 | 
					    example: '@weekly',
 | 
				
			||||||
        "equivalent": "0 0 * * 0"
 | 
					    equivalent: '0 0 * * 0',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@daily",
 | 
					    symbol: '@daily',
 | 
				
			||||||
        "meaning": "Once a day at midnight",
 | 
					    meaning: 'Once a day at midnight',
 | 
				
			||||||
        "example": "@daily",
 | 
					    example: '@daily',
 | 
				
			||||||
        "equivalent": "0 0 * * *"
 | 
					    equivalent: '0 0 * * *',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@midnight",
 | 
					    symbol: '@midnight',
 | 
				
			||||||
        "meaning": "Same as @daily",
 | 
					    meaning: 'Same as @daily',
 | 
				
			||||||
        "example": "@midnight",
 | 
					    example: '@midnight',
 | 
				
			||||||
        "equivalent": "0 0 * * *"
 | 
					    equivalent: '0 0 * * *',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@hourly",
 | 
					    symbol: '@hourly',
 | 
				
			||||||
        "meaning": "Once an hour at the beginning of the hour",
 | 
					    meaning: 'Once an hour at the beginning of the hour',
 | 
				
			||||||
        "example": "@hourly",
 | 
					    example: '@hourly',
 | 
				
			||||||
        "equivalent": "0 * * * *"
 | 
					    equivalent: '0 * * * *',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
        "symbol": "@reboot",
 | 
					    symbol: '@reboot',
 | 
				
			||||||
        "meaning": "Run at startup",
 | 
					    meaning: 'Run at startup',
 | 
				
			||||||
        "example": "",
 | 
					    example: '',
 | 
				
			||||||
        "equivalent": ""
 | 
					    equivalent: '',
 | 
				
			||||||
    }
 | 
					  },
 | 
				
			||||||
]
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cronString = computed(() => {
 | 
					const cronString = computed(() => {
 | 
				
			||||||
  if (isCronValid(cron.value)) {
 | 
					  if (isCronValid(cron.value)) {
 | 
				
			||||||
    return cronstrue.toString(cron.value, cronstrueConfig)
 | 
					    return cronstrue.toString(cron.value, cronstrueConfig);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return ' '
 | 
					  return ' ';
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cronValidation = useValidation({
 | 
					const cronValidation = useValidation({
 | 
				
			||||||
  source: cron,
 | 
					  source: cron,
 | 
				
			||||||
  rules: [{
 | 
					  rules: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
      validator: (value) => isCronValid(value),
 | 
					      validator: (value) => isCronValid(value),
 | 
				
			||||||
    message: 'This cron is invalid'
 | 
					      message: 'This cron is invalid',
 | 
				
			||||||
  }]
 | 
					    },
 | 
				
			||||||
})
 | 
					  ],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
@@ -239,7 +208,6 @@ const cronValidation = useValidation({
 | 
				
			|||||||
    font-size: 30px;
 | 
					    font-size: 30px;
 | 
				
			||||||
    font-family: monospace;
 | 
					    font-family: monospace;
 | 
				
			||||||
    padding: 5px;
 | 
					    padding: 5px;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,25 @@
 | 
				
			|||||||
import { Alarm } from '@vicons/tabler';
 | 
					import { Alarm } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Crontab generator',
 | 
					  name: 'Crontab generator',
 | 
				
			||||||
  path: '/crontab-generator',
 | 
					  path: '/crontab-generator',
 | 
				
			||||||
  description: 'Validate and generate crontab and get the human readable description of the cron schedule.',
 | 
					  description: 'Validate and generate crontab and get the human readable description of the cron schedule.',
 | 
				
			||||||
  keywords: ['crontab', 'generator', 'cronjob', 'cron', 'schedule', 'parse', 'expression', 'year', 'month', 'week', 'day', 'minute', 'second'],
 | 
					  keywords: [
 | 
				
			||||||
 | 
					    'crontab',
 | 
				
			||||||
 | 
					    'generator',
 | 
				
			||||||
 | 
					    'cronjob',
 | 
				
			||||||
 | 
					    'cron',
 | 
				
			||||||
 | 
					    'schedule',
 | 
				
			||||||
 | 
					    'parse',
 | 
				
			||||||
 | 
					    'expression',
 | 
				
			||||||
 | 
					    'year',
 | 
				
			||||||
 | 
					    'month',
 | 
				
			||||||
 | 
					    'week',
 | 
				
			||||||
 | 
					    'day',
 | 
				
			||||||
 | 
					    'minute',
 | 
				
			||||||
 | 
					    'second',
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  component: () => import('./crontab-generator.vue'),
 | 
					  component: () => import('./crontab-generator.vue'),
 | 
				
			||||||
  icon: Alarm,
 | 
					  icon: Alarm,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,11 +2,7 @@
 | 
				
			|||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <n-card>
 | 
					    <n-card>
 | 
				
			||||||
      <n-space justify="center">
 | 
					      <n-space justify="center">
 | 
				
			||||||
        <n-form-item
 | 
					        <n-form-item label="Use current date-time ?" label-placement="left" :show-feedback="false">
 | 
				
			||||||
          label="Use current date-time ?"
 | 
					 | 
				
			||||||
          label-placement="left"
 | 
					 | 
				
			||||||
          :show-feedback="false"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <n-switch v-model:value="useCurrentDate" />
 | 
					          <n-switch v-model:value="useCurrentDate" />
 | 
				
			||||||
        </n-form-item>
 | 
					        </n-form-item>
 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
@@ -14,10 +10,10 @@
 | 
				
			|||||||
        :feedback="inputInvalid ? 'Invalid date for the current format' : ''"
 | 
					        :feedback="inputInvalid ? 'Invalid date for the current format' : ''"
 | 
				
			||||||
        :validation-status="inputInvalid ? 'error' : undefined"
 | 
					        :validation-status="inputInvalid ? 'error' : undefined"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <n-input-group style="flex-grow: 1;">
 | 
					        <n-input-group style="flex-grow: 1">
 | 
				
			||||||
          <n-select
 | 
					          <n-select
 | 
				
			||||||
            v-model:value="inputFormat"
 | 
					            v-model:value="inputFormat"
 | 
				
			||||||
            style="width: 200px;"
 | 
					            style="width: 200px"
 | 
				
			||||||
            :options="formats.map(({ name }, i) => ({ label: name, value: i }))"
 | 
					            :options="formats.map(({ name }, i) => ({ label: name, value: i }))"
 | 
				
			||||||
            :disabled="useCurrentDate"
 | 
					            :disabled="useCurrentDate"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
@@ -30,16 +26,10 @@
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        </n-input-group>
 | 
					        </n-input-group>
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-divider style="margin-top: 0;" />
 | 
					      <n-divider style="margin-top: 0" />
 | 
				
			||||||
      <div
 | 
					      <div v-for="{ name, fromDate } in formats" :key="name" style="margin: 5px 0">
 | 
				
			||||||
        v-for="{ name, fromDate } in formats"
 | 
					 | 
				
			||||||
        :key="name"
 | 
					 | 
				
			||||||
        style="margin: 5px 0;"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-input-group>
 | 
					        <n-input-group>
 | 
				
			||||||
          <n-input-group-label style="width: 200px;">
 | 
					          <n-input-group-label style="flex: 0 0 170px"> {{ name }}: </n-input-group-label>
 | 
				
			||||||
            {{ name }}
 | 
					 | 
				
			||||||
          </n-input-group-label>
 | 
					 | 
				
			||||||
          <input-copyable :value="fromDate(baseDate)" />
 | 
					          <input-copyable :value="fromDate(baseDate)" />
 | 
				
			||||||
        </n-input-group>
 | 
					        </n-input-group>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@@ -49,80 +39,98 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useRafFn } from '@vueuse/core';
 | 
					import { useRafFn } from '@vueuse/core';
 | 
				
			||||||
import { formatISO, formatISO9075, formatRFC3339, formatRFC7231, fromUnixTime, getTime, getUnixTime, isDate, parseISO, parseJSON } from 'date-fns';
 | 
					import {
 | 
				
			||||||
import { ref } from 'vue'
 | 
					  formatISO,
 | 
				
			||||||
import InputCopyable from "../../components/InputCopyable.vue";
 | 
					  formatISO9075,
 | 
				
			||||||
 | 
					  formatRFC3339,
 | 
				
			||||||
 | 
					  formatRFC7231,
 | 
				
			||||||
 | 
					  fromUnixTime,
 | 
				
			||||||
 | 
					  getTime,
 | 
				
			||||||
 | 
					  getUnixTime,
 | 
				
			||||||
 | 
					  isDate,
 | 
				
			||||||
 | 
					  parseISO,
 | 
				
			||||||
 | 
					  parseJSON,
 | 
				
			||||||
 | 
					} from 'date-fns';
 | 
				
			||||||
 | 
					import { ref } from 'vue';
 | 
				
			||||||
 | 
					import InputCopyable from '../../components/InputCopyable.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const useCurrentDate = ref(true)
 | 
					const useCurrentDate = ref(true);
 | 
				
			||||||
const inputDate = ref('')
 | 
					const inputDate = ref('');
 | 
				
			||||||
const inputFormat = ref(6)
 | 
					const inputFormat = ref(6);
 | 
				
			||||||
const inputInvalid = ref(false)
 | 
					const inputInvalid = ref(false);
 | 
				
			||||||
const baseDate = ref(new Date())
 | 
					const baseDate = ref(new Date());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
useRafFn(() => {
 | 
					useRafFn(() => {
 | 
				
			||||||
  if (useCurrentDate.value) {
 | 
					  if (useCurrentDate.value) {
 | 
				
			||||||
        baseDate.value = new Date()
 | 
					    baseDate.value = new Date();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function onDateInputChanged(value: string) {
 | 
					function onDateInputChanged(value: string) {
 | 
				
			||||||
    const { toDate } = formats[inputFormat.value]
 | 
					  const { toDate } = formats[inputFormat.value];
 | 
				
			||||||
    inputInvalid.value = false
 | 
					  inputInvalid.value = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
        const formatted: Date | string = toDate(value)
 | 
					    const formatted: Date | string = toDate(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!isDate(formatted) || isNaN(formatted.getTime())) {
 | 
					    if (!isDate(formatted) || isNaN(formatted.getTime())) {
 | 
				
			||||||
            throw new Error('Invalid date')
 | 
					      throw new Error('Invalid date');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        baseDate.value = formatted
 | 
					    baseDate.value = formatted;
 | 
				
			||||||
  } catch (_) {
 | 
					  } catch (_) {
 | 
				
			||||||
        inputInvalid.value = true
 | 
					    inputInvalid.value = true;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const formats = [
 | 
					type Format = {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  fromDate: (date: Date) => string;
 | 
				
			||||||
 | 
					  toDate: (value: string) => Date;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const toDate: Format['toDate'] = (date) => new Date(date);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const formats: Format[] = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'JS locale date string',
 | 
					    name: 'JS locale date string',
 | 
				
			||||||
        fromDate: (date: Date) => date.toString(),
 | 
					    fromDate: (date) => date.toString(),
 | 
				
			||||||
        toDate: (date: string) => new Date(date)
 | 
					    toDate,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'ISO 8601',
 | 
					    name: 'ISO 8601',
 | 
				
			||||||
        fromDate: (date: Date) => formatISO(date),
 | 
					    fromDate: formatISO,
 | 
				
			||||||
        toDate: (date: string) => parseISO(date)
 | 
					    toDate: parseISO,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'ISO 9075',
 | 
					    name: 'ISO 9075',
 | 
				
			||||||
        fromDate: (date: Date) => formatISO9075(date),
 | 
					    fromDate: formatISO9075,
 | 
				
			||||||
        toDate: (date: string) => parseISO(date)
 | 
					    toDate: parseISO,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'RFC 3339',
 | 
					    name: 'RFC 3339',
 | 
				
			||||||
        fromDate: (date: Date) => formatRFC3339(date),
 | 
					    fromDate: formatRFC3339,
 | 
				
			||||||
        toDate: (date: string) => new Date(date)
 | 
					    toDate,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'RFC 7231',
 | 
					    name: 'RFC 7231',
 | 
				
			||||||
        fromDate: (date: Date) => formatRFC7231(date),
 | 
					    fromDate: formatRFC7231,
 | 
				
			||||||
        toDate: (date: string) => new Date(date)
 | 
					    toDate,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'Timestamp',
 | 
					    name: 'Timestamp',
 | 
				
			||||||
        fromDate: (date: Date) => String(getTime(date)),
 | 
					    fromDate: (date) => String(getTime(date)),
 | 
				
			||||||
        toDate: (ms: string) => parseJSON(+ms)
 | 
					    toDate: (ms) => parseJSON(+ms),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'Unix timestamp',
 | 
					    name: 'Unix timestamp',
 | 
				
			||||||
        fromDate: (date: Date) => String(getUnixTime(date)),
 | 
					    fromDate: (date) => String(getUnixTime(date)),
 | 
				
			||||||
        toDate: (sec: string) => fromUnixTime(+sec)
 | 
					    toDate: (sec) => fromUnixTime(+sec),
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'UTC format',
 | 
					    name: 'UTC format',
 | 
				
			||||||
        fromDate: (date: Date) => date.toUTCString(),
 | 
					    fromDate: (date) => date.toUTCString(),
 | 
				
			||||||
        toDate: (date: string) => new Date(date)
 | 
					    toDate,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
]
 | 
					];
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { Calendar } from '@vicons/tabler';
 | 
					import { Calendar } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Date-time converter',
 | 
					  name: 'Date-time converter',
 | 
				
			||||||
  path: '/date-converter',
 | 
					  path: '/date-converter',
 | 
				
			||||||
  description: 'Convert date and time into the various different formats',
 | 
					  description: 'Convert date and time into the various different formats',
 | 
				
			||||||
  keywords: ['date', 'time', 'converter', 'iso', 'utc', 'timezone', 'year', 'mounth', 'day', 'minute', 'seconde'],
 | 
					  keywords: ['date', 'time', 'converter', 'iso', 'utc', 'timezone', 'year', 'month', 'day', 'minute', 'seconde'],
 | 
				
			||||||
  component: () => import('./date-time-converter.vue'),
 | 
					  component: () => import('./date-time-converter.vue'),
 | 
				
			||||||
  icon: Calendar,
 | 
					  icon: Calendar,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,8 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card
 | 
					  <n-card v-for="{ name, information } in sections" :key="name" :title="name">
 | 
				
			||||||
    v-for="{name, information} in sections"
 | 
					    <n-grid cols="1 400:2" x-gap="12" y-gap="12">
 | 
				
			||||||
    :key="name"
 | 
					      <n-gi v-for="{ label, value } in information" :key="label" class="information">
 | 
				
			||||||
    :title="name"
 | 
					        <n-card :bordered="false" embedded>
 | 
				
			||||||
    style="margin-bottom: 15px;"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <n-grid
 | 
					 | 
				
			||||||
      cols="1 400:2"
 | 
					 | 
				
			||||||
      x-gap="12"
 | 
					 | 
				
			||||||
      y-gap="12"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-gi
 | 
					 | 
				
			||||||
        v-for="{label, value} in information"
 | 
					 | 
				
			||||||
        :key="label"
 | 
					 | 
				
			||||||
        class="information"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-card
 | 
					 | 
				
			||||||
          :bordered="false"
 | 
					 | 
				
			||||||
          embedded
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <div class="label">
 | 
					          <div class="label">
 | 
				
			||||||
            {{ label }}
 | 
					            {{ label }}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@@ -35,10 +19,10 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useWindowSize } from '@vueuse/core'
 | 
					import { useWindowSize } from '@vueuse/core';
 | 
				
			||||||
import { computed } from 'vue';
 | 
					import { computed } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { width, height } = useWindowSize()
 | 
					const { width, height } = useWindowSize();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sections = [
 | 
					const sections = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -46,59 +30,56 @@ const sections = [
 | 
				
			|||||||
    information: [
 | 
					    information: [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Screen size',
 | 
					        label: 'Screen size',
 | 
				
			||||||
        value: computed(() => `${window.screen.availWidth} x ${window.screen.availHeight}`)
 | 
					        value: computed(() => `${window.screen.availWidth} x ${window.screen.availHeight}`),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Orientation',
 | 
					        label: 'Orientation',
 | 
				
			||||||
        value:  computed(() => window.screen.orientation.type)
 | 
					        value: computed(() => window.screen.orientation.type),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Orientation angle',
 | 
					        label: 'Orientation angle',
 | 
				
			||||||
        value:  computed(() => `${window.screen.orientation.angle}°`)
 | 
					        value: computed(() => `${window.screen.orientation.angle}°`),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Color depth',
 | 
					        label: 'Color depth',
 | 
				
			||||||
        value:  computed(() => `${window.screen.colorDepth} bits`)
 | 
					        value: computed(() => `${window.screen.colorDepth} bits`),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Pixel ratio',
 | 
					        label: 'Pixel ratio',
 | 
				
			||||||
        value:  computed(() => `${window.devicePixelRatio} dppx`)
 | 
					        value: computed(() => `${window.devicePixelRatio} dppx`),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Window size',
 | 
					        label: 'Window size',
 | 
				
			||||||
        value: computed(() => `${width.value} x ${height.value}`)
 | 
					        value: computed(() => `${width.value} x ${height.value}`),
 | 
				
			||||||
      }
 | 
					      },
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'Device',
 | 
					    name: 'Device',
 | 
				
			||||||
    information: [
 | 
					    information: [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Browser vendor',
 | 
					        label: 'Browser vendor',
 | 
				
			||||||
        value: computed(() => navigator.vendor)
 | 
					        value: computed(() => navigator.vendor),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Languages',
 | 
					        label: 'Languages',
 | 
				
			||||||
        value: computed(() => navigator.languages.join(', '))
 | 
					        value: computed(() => navigator.languages.join(', ')),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'Plateform',
 | 
					        label: 'Platform',
 | 
				
			||||||
        value: computed(() => navigator.platform)
 | 
					        value: computed(() => navigator.platform),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        label: 'User agent',
 | 
					        label: 'User agent',
 | 
				
			||||||
        value: computed(() => navigator.userAgent)
 | 
					        value: computed(() => navigator.userAgent),
 | 
				
			||||||
      }
 | 
					      },
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
  }
 | 
					  },
 | 
				
			||||||
]
 | 
					];
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
.information {
 | 
					.information {
 | 
				
			||||||
 | 
					 | 
				
			||||||
  .label {
 | 
					  .label {
 | 
				
			||||||
    font-size: 14px;
 | 
					    font-size: 14px;
 | 
				
			||||||
    opacity: 0.8;
 | 
					    opacity: 0.8;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,23 @@
 | 
				
			|||||||
import { DeviceDesktop } from '@vicons/tabler';
 | 
					import { DeviceDesktop } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Device information',
 | 
					  name: 'Device information',
 | 
				
			||||||
  path: '/device-information',
 | 
					  path: '/device-information',
 | 
				
			||||||
  description: 'Get information about your current device (screen size, pixel-ratio, user agent, ...)',
 | 
					  description: 'Get information about your current device (screen size, pixel-ratio, user agent, ...)',
 | 
				
			||||||
  keywords: ['device', 'information', 'screen', 'pixel', 'ratio', 'status', 'data', 'computer', 'size', 'user', 'agent'],
 | 
					  keywords: [
 | 
				
			||||||
 | 
					    'device',
 | 
				
			||||||
 | 
					    'information',
 | 
				
			||||||
 | 
					    'screen',
 | 
				
			||||||
 | 
					    'pixel',
 | 
				
			||||||
 | 
					    'ratio',
 | 
				
			||||||
 | 
					    'status',
 | 
				
			||||||
 | 
					    'data',
 | 
				
			||||||
 | 
					    'computer',
 | 
				
			||||||
 | 
					    'size',
 | 
				
			||||||
 | 
					    'user',
 | 
				
			||||||
 | 
					    'agent',
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  component: () => import('./device-information.vue'),
 | 
					  component: () => import('./device-information.vue'),
 | 
				
			||||||
  icon: DeviceDesktop,
 | 
					  icon: DeviceDesktop,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,7 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					 | 
				
			||||||
  <n-card title="Encrypt">
 | 
					  <n-card title="Encrypt">
 | 
				
			||||||
    <n-space item-style="flex: 1 1 0">
 | 
					    <n-space item-style="flex: 1 1 0">
 | 
				
			||||||
        <n-form-item
 | 
					      <n-form-item label="Your text:" :show-feedback="false">
 | 
				
			||||||
          label="Your text:"
 | 
					 | 
				
			||||||
          :show-feedback="false"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
        <n-input
 | 
					        <n-input
 | 
				
			||||||
          v-model:value="cypherInput"
 | 
					          v-model:value="cypherInput"
 | 
				
			||||||
          type="textarea"
 | 
					          type="textarea"
 | 
				
			||||||
@@ -14,28 +10,19 @@
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-space vertical>
 | 
					      <n-space vertical>
 | 
				
			||||||
          <n-form-item
 | 
					        <n-form-item label="Your secret key:" :show-feedback="false">
 | 
				
			||||||
            label="Your secret key:"
 | 
					 | 
				
			||||||
            :show-feedback="false"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
          <n-input v-model:value="cypherSecret" />
 | 
					          <n-input v-model:value="cypherSecret" />
 | 
				
			||||||
        </n-form-item>
 | 
					        </n-form-item>
 | 
				
			||||||
          <n-form-item
 | 
					        <n-form-item label="Encryption algorithm:" :show-feedback="false">
 | 
				
			||||||
            label="Encryption algorithm:"
 | 
					 | 
				
			||||||
            :show-feedback="false"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
          <n-select
 | 
					          <n-select
 | 
				
			||||||
            v-model:value="cypherAlgo"
 | 
					            v-model:value="cypherAlgo"
 | 
				
			||||||
              :options="Object.keys(algos).map(label => ({ label, value: label }))"
 | 
					            :options="Object.keys(algos).map((label) => ({ label, value: label }))"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </n-form-item>
 | 
					        </n-form-item>
 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
      <br>
 | 
					    <br />
 | 
				
			||||||
      <n-form-item
 | 
					    <n-form-item label="Your text encrypted:" :show-feedback="false">
 | 
				
			||||||
        label="Yout text encrypted:"
 | 
					 | 
				
			||||||
        :show-feedback="false"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
      <n-input
 | 
					      <n-input
 | 
				
			||||||
        :value="cypherOutput"
 | 
					        :value="cypherOutput"
 | 
				
			||||||
        type="textarea"
 | 
					        type="textarea"
 | 
				
			||||||
@@ -49,13 +36,9 @@
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
  <n-card title="Decrypt">
 | 
					  <n-card title="Decrypt">
 | 
				
			||||||
    <n-space item-style="flex: 1 1 0">
 | 
					    <n-space item-style="flex: 1 1 0">
 | 
				
			||||||
        <n-form-item
 | 
					      <n-form-item label="Your encrypted text:" :show-feedback="false">
 | 
				
			||||||
          label="Your encrypted text:"
 | 
					 | 
				
			||||||
          :show-feedback="false"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
        <n-input
 | 
					        <n-input
 | 
				
			||||||
          v-model:value="decryptInput"
 | 
					          v-model:value="decryptInput"
 | 
				
			||||||
          type="textarea"
 | 
					          type="textarea"
 | 
				
			||||||
@@ -64,28 +47,19 @@
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
      <n-space vertical>
 | 
					      <n-space vertical>
 | 
				
			||||||
          <n-form-item
 | 
					        <n-form-item label="Your secret key:" :show-feedback="false">
 | 
				
			||||||
            label="Your secret key:"
 | 
					 | 
				
			||||||
            :show-feedback="false"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
          <n-input v-model:value="decryptSecret" />
 | 
					          <n-input v-model:value="decryptSecret" />
 | 
				
			||||||
        </n-form-item>
 | 
					        </n-form-item>
 | 
				
			||||||
          <n-form-item
 | 
					        <n-form-item label="Encryption algorithm:" :show-feedback="false">
 | 
				
			||||||
            label="Encryption algorithm:"
 | 
					 | 
				
			||||||
            :show-feedback="false"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
          <n-select
 | 
					          <n-select
 | 
				
			||||||
            v-model:value="decryptAlgo"
 | 
					            v-model:value="decryptAlgo"
 | 
				
			||||||
              :options="Object.keys(algos).map(label => ({ label, value: label }))"
 | 
					            :options="Object.keys(algos).map((label) => ({ label, value: label }))"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </n-form-item>
 | 
					        </n-form-item>
 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
      <br>
 | 
					    <br />
 | 
				
			||||||
      <n-form-item
 | 
					    <n-form-item label="Your decrypted text:" :show-feedback="false">
 | 
				
			||||||
        label="Yout decrypted text:"
 | 
					 | 
				
			||||||
        :show-feedback="false"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
      <n-input
 | 
					      <n-input
 | 
				
			||||||
        :value="decryptOutput"
 | 
					        :value="decryptOutput"
 | 
				
			||||||
        type="textarea"
 | 
					        type="textarea"
 | 
				
			||||||
@@ -99,25 +73,23 @@
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { computed, ref } from 'vue'
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
import { AES, TripleDES, Rabbit, RC4, enc } from 'crypto-js'
 | 
					import { AES, TripleDES, Rabbit, RC4, enc } from 'crypto-js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const algos = { AES, TripleDES, Rabbit, RC4 }
 | 
					const algos = { AES, TripleDES, Rabbit, RC4 };
 | 
				
			||||||
 | 
					 | 
				
			||||||
const cypherInput = ref('Lorem ipsum dolor sit amet')
 | 
					 | 
				
			||||||
const cypherAlgo = ref<keyof typeof algos>('AES')
 | 
					 | 
				
			||||||
const cypherSecret = ref('my secret key')
 | 
					 | 
				
			||||||
const cypherOutput = computed(() => algos[cypherAlgo.value].encrypt(cypherInput.value, cypherSecret.value).toString())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const decryptInput = ref('U2FsdGVkX1/EC3+6P5dbbkZ3e1kQ5o2yzuU0NHTjmrKnLBEwreV489Kr0DIB+uBs')
 | 
					 | 
				
			||||||
const decryptAlgo = ref<keyof typeof algos>('AES')
 | 
					 | 
				
			||||||
const decryptSecret = ref('my secret key')
 | 
					 | 
				
			||||||
const decryptOutput = computed(() => algos[decryptAlgo.value].decrypt(decryptInput.value, decryptSecret.value).toString(enc.Utf8))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const cypherInput = ref('Lorem ipsum dolor sit amet');
 | 
				
			||||||
 | 
					const cypherAlgo = ref<keyof typeof algos>('AES');
 | 
				
			||||||
 | 
					const cypherSecret = ref('my secret key');
 | 
				
			||||||
 | 
					const cypherOutput = computed(() => algos[cypherAlgo.value].encrypt(cypherInput.value, cypherSecret.value).toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const decryptInput = ref('U2FsdGVkX1/EC3+6P5dbbkZ3e1kQ5o2yzuU0NHTjmrKnLBEwreV489Kr0DIB+uBs');
 | 
				
			||||||
 | 
					const decryptAlgo = ref<keyof typeof algos>('AES');
 | 
				
			||||||
 | 
					const decryptSecret = ref('my secret key');
 | 
				
			||||||
 | 
					const decryptOutput = computed(() =>
 | 
				
			||||||
 | 
					  algos[decryptAlgo.value].decrypt(decryptInput.value, decryptSecret.value).toString(enc.Utf8),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,12 @@
 | 
				
			|||||||
import { Lock } from '@vicons/tabler';
 | 
					import { Lock } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Encrypt / decrypt text',
 | 
					  name: 'Encrypt / decrypt text',
 | 
				
			||||||
  path: '/encryption',
 | 
					  path: '/encryption',
 | 
				
			||||||
  description: 'Encrypt and decrypt text clear text using crypto algorithm like AES, TripleDES, Rabbit or RC4.',
 | 
					  description: 'Encrypt and decrypt text clear text using crypto algorithm like AES, TripleDES, Rabbit or RC4.',
 | 
				
			||||||
  keywords: ['cypher', 'uncypher', 'text', 'AES', 'TripleDES', 'Rabbit', 'RC4'],
 | 
					  keywords: ['cypher', 'encipher', 'text', 'AES', 'TripleDES', 'Rabbit', 'RC4'],
 | 
				
			||||||
  component: () => import('./encryption.vue'),
 | 
					  component: () => import('./encryption.vue'),
 | 
				
			||||||
  icon: Lock,
 | 
					  icon: Lock,
 | 
				
			||||||
  redirectFrom: ['/cypher'],
 | 
					  redirectFrom: ['/cypher'],
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,10 +5,10 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import Memo from './git-memo.md'
 | 
					import Memo from './git-memo.md';
 | 
				
			||||||
import { useThemeVars } from 'naive-ui'
 | 
					import { useThemeVars } from 'naive-ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const themeVars = useThemeVars()
 | 
					const themeVars = useThemeVars();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
import { BrandGit } from '@vicons/tabler';
 | 
					import { BrandGit } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Git cheatsheet',
 | 
					  name: 'Git cheatsheet',
 | 
				
			||||||
  path: '/git-memo',
 | 
					  path: '/git-memo',
 | 
				
			||||||
  description: 'Git is a decentralized version management sofware. With this cheatsheet you will have a quick acces to the most common git commands.',
 | 
					  description:
 | 
				
			||||||
  keywords: ['git', 'push', 'force', 'pull', 'commit', 'ammend', 'rebase', 'merge', 'reset', 'soft', 'hard', 'lease'],
 | 
					    'Git is a decentralized version management software. With this cheatsheet you will have a quick access to the most common git commands.',
 | 
				
			||||||
 | 
					  keywords: ['git', 'push', 'force', 'pull', 'commit', 'amend', 'rebase', 'merge', 'reset', 'soft', 'hard', 'lease'],
 | 
				
			||||||
  component: () => import('./git-memo.vue'),
 | 
					  component: () => import('./git-memo.vue'),
 | 
				
			||||||
  icon: BrandGit,
 | 
					  icon: BrandGit,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,60 +1,24 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <n-card>
 | 
					    <n-card>
 | 
				
			||||||
      <n-input
 | 
					      <n-input v-model:value="clearText" type="textarea" placeholder="Your string..." :autosize="{ minRows: 3 }" />
 | 
				
			||||||
        v-model:value="clearText"
 | 
					 | 
				
			||||||
        type="textarea"
 | 
					 | 
				
			||||||
        placeholder="Your string..."
 | 
					 | 
				
			||||||
        :autosize="{ minRows: 3 }"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <br>
 | 
					 | 
				
			||||||
      <br>
 | 
					 | 
				
			||||||
      <n-select
 | 
					 | 
				
			||||||
        v-model:value="algo"
 | 
					 | 
				
			||||||
        :options="Object.keys(algos).map(label => ({ label, value: label }))"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <br>
 | 
					      <n-divider />
 | 
				
			||||||
      <n-input
 | 
					
 | 
				
			||||||
        style="text-align: center;"
 | 
					      <div v-for="algo in algoNames" :key="algo" style="margin: 5px 0">
 | 
				
			||||||
        :value="hashedText"
 | 
					        <n-input-group>
 | 
				
			||||||
        type="textarea"
 | 
					          <n-input-group-label style="flex: 0 0 120px"> {{ algo }} </n-input-group-label>
 | 
				
			||||||
        placeholder="Your string hash"
 | 
					          <input-copyable :value="hashText(algo, clearText)" readonly />
 | 
				
			||||||
        :autosize="{ minRows: 1 }"
 | 
					        </n-input-group>
 | 
				
			||||||
        readonly
 | 
					      </div>
 | 
				
			||||||
        autocomplete="off"
 | 
					 | 
				
			||||||
        autocorrect="off"
 | 
					 | 
				
			||||||
        autocapitalize="off"
 | 
					 | 
				
			||||||
        spellcheck="false"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <br>
 | 
					 | 
				
			||||||
      <br>
 | 
					 | 
				
			||||||
      <n-space justify="center">
 | 
					 | 
				
			||||||
        <n-button
 | 
					 | 
				
			||||||
          secondary
 | 
					 | 
				
			||||||
          autofocus
 | 
					 | 
				
			||||||
          @click="copy"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Copy
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					 | 
				
			||||||
    </n-card>
 | 
					    </n-card>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import InputCopyable from '../../components/InputCopyable.vue';
 | 
				
			||||||
import { ref, computed } from 'vue'
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import {
 | 
					import { MD5, SHA1, SHA256, SHA224, SHA512, SHA384, SHA3, RIPEMD160 } from 'crypto-js';
 | 
				
			||||||
    MD5,
 | 
					 | 
				
			||||||
    SHA1,
 | 
					 | 
				
			||||||
    SHA256,
 | 
					 | 
				
			||||||
    SHA224,
 | 
					 | 
				
			||||||
    SHA512,
 | 
					 | 
				
			||||||
    SHA384,
 | 
					 | 
				
			||||||
    SHA3,
 | 
					 | 
				
			||||||
    RIPEMD160,
 | 
					 | 
				
			||||||
} from 'crypto-js'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const algos = {
 | 
					const algos = {
 | 
				
			||||||
  MD5,
 | 
					  MD5,
 | 
				
			||||||
@@ -67,9 +31,11 @@ const algos = {
 | 
				
			|||||||
  RIPEMD160,
 | 
					  RIPEMD160,
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const clearText = ref('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lacus metus blandit dolor lacus natoque ad fusce aliquam velit.')
 | 
					type AlgoNames = keyof typeof algos;
 | 
				
			||||||
const algo = ref<keyof typeof algos>('SHA256')
 | 
					const algoNames = Object.keys(algos) as AlgoNames[];
 | 
				
			||||||
const hashedText = computed(() => algos[algo.value](clearText.value).toString())
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { copy } = useCopy({ source: hashedText, text: 'Hash copied to the clipboard' })
 | 
					const clearText = ref(
 | 
				
			||||||
 | 
					  'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lacus metus blandit dolor lacus natoque ad fusce aliquam velit.',
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					const hashText = (algo: AlgoNames, value: string) => algos[algo](value).toString();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,27 @@
 | 
				
			|||||||
import { EyeOff } from '@vicons/tabler';
 | 
					import { EyeOff } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Hash text',
 | 
					  name: 'Hash text',
 | 
				
			||||||
  path: '/hash-text',
 | 
					  path: '/hash-text',
 | 
				
			||||||
  description: 'Hash a text string using the function you need : MD5, SHA1, SHA256, SHA224, SHA512, SHA384, SHA3 or RIPEMD160',
 | 
					  description:
 | 
				
			||||||
  keywords: ['hash', 'digest', 'crypto', 'security', 'text', 'MD5', 'SHA1', 'SHA256', 'SHA224', 'SHA512', 'SHA384', 'SHA3', 'RIPEMD160'],
 | 
					    'Hash a text string using the function you need : MD5, SHA1, SHA256, SHA224, SHA512, SHA384, SHA3 or RIPEMD160',
 | 
				
			||||||
 | 
					  keywords: [
 | 
				
			||||||
 | 
					    'hash',
 | 
				
			||||||
 | 
					    'digest',
 | 
				
			||||||
 | 
					    'crypto',
 | 
				
			||||||
 | 
					    'security',
 | 
				
			||||||
 | 
					    'text',
 | 
				
			||||||
 | 
					    'MD5',
 | 
				
			||||||
 | 
					    'SHA1',
 | 
				
			||||||
 | 
					    'SHA256',
 | 
				
			||||||
 | 
					    'SHA224',
 | 
				
			||||||
 | 
					    'SHA512',
 | 
				
			||||||
 | 
					    'SHA384',
 | 
				
			||||||
 | 
					    'SHA3',
 | 
				
			||||||
 | 
					    'RIPEMD160',
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  component: () => import('./hash-text.vue'),
 | 
					  component: () => import('./hash-text.vue'),
 | 
				
			||||||
  icon: EyeOff,
 | 
					  icon: EyeOff,
 | 
				
			||||||
  redirectFrom: ['/hash'],
 | 
					  redirectFrom: ['/hash'],
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										64
									
								
								src/tools/html-entities/html-entities.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/tools/html-entities/html-entities.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <n-card title="Escape html entities">
 | 
				
			||||||
 | 
					    <n-form-item label="Your string :">
 | 
				
			||||||
 | 
					      <n-input
 | 
				
			||||||
 | 
					        v-model:value="escapeInput"
 | 
				
			||||||
 | 
					        type="textarea"
 | 
				
			||||||
 | 
					        placeholder="The string to escape"
 | 
				
			||||||
 | 
					        :autosize="{ minRows: 2 }"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-form-item label="Your string escaped :">
 | 
				
			||||||
 | 
					      <n-input
 | 
				
			||||||
 | 
					        type="textarea"
 | 
				
			||||||
 | 
					        readonly
 | 
				
			||||||
 | 
					        placeholder="Your string escaped"
 | 
				
			||||||
 | 
					        :value="escapeOutput"
 | 
				
			||||||
 | 
					        :autosize="{ minRows: 2 }"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-space justify="center">
 | 
				
			||||||
 | 
					      <n-button secondary @click="copyEscaped"> Copy </n-button>
 | 
				
			||||||
 | 
					    </n-space>
 | 
				
			||||||
 | 
					  </n-card>
 | 
				
			||||||
 | 
					  <n-card title="Unescape html entities">
 | 
				
			||||||
 | 
					    <n-form-item label="Your escaped string :">
 | 
				
			||||||
 | 
					      <n-input
 | 
				
			||||||
 | 
					        v-model:value="unescapeInput"
 | 
				
			||||||
 | 
					        type="textarea"
 | 
				
			||||||
 | 
					        placeholder="The string to unescape"
 | 
				
			||||||
 | 
					        :autosize="{ minRows: 2 }"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-form-item label="Your string unescaped :">
 | 
				
			||||||
 | 
					      <n-input
 | 
				
			||||||
 | 
					        :value="unescapeOutput"
 | 
				
			||||||
 | 
					        type="textarea"
 | 
				
			||||||
 | 
					        readonly
 | 
				
			||||||
 | 
					        placeholder="Your string unescaped"
 | 
				
			||||||
 | 
					        :autosize="{ minRows: 2 }"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-space justify="center">
 | 
				
			||||||
 | 
					      <n-button secondary @click="copyUnescaped"> Copy </n-button>
 | 
				
			||||||
 | 
					    </n-space>
 | 
				
			||||||
 | 
					  </n-card>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { escape, unescape } from 'lodash';
 | 
				
			||||||
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const escapeInput = ref('<title>IT Tool</title>');
 | 
				
			||||||
 | 
					const escapeOutput = computed(() => escape(escapeInput.value));
 | 
				
			||||||
 | 
					const { copy: copyEscaped } = useCopy({ source: escapeOutput });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const unescapeInput = ref('<title>IT Tool</title');
 | 
				
			||||||
 | 
					const unescapeOutput = computed(() => unescape(unescapeInput.value));
 | 
				
			||||||
 | 
					const { copy: copyUnescaped } = useCopy({ source: unescapeOutput });
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										11
									
								
								src/tools/html-entities/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/tools/html-entities/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { Code } from '@vicons/tabler';
 | 
				
			||||||
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const tool = defineTool({
 | 
				
			||||||
 | 
					  name: 'Escape html entities',
 | 
				
			||||||
 | 
					  path: '/html-entities',
 | 
				
			||||||
 | 
					  description: 'Escape or unescape html entities (replace <,>, &, " and \' to their html version)',
 | 
				
			||||||
 | 
					  keywords: ['html', 'entities', 'escape', 'unescape', 'special', 'characters', 'tags'],
 | 
				
			||||||
 | 
					  component: () => import('./html-entities.vue'),
 | 
				
			||||||
 | 
					  icon: Code,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,6 +1,10 @@
 | 
				
			|||||||
import { LockOpen } from '@vicons/tabler';
 | 
					import { LockOpen } from '@vicons/tabler';
 | 
				
			||||||
import type { ToolCategory } from './Tool';
 | 
					import type { ToolCategory } from './tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { tool as mathEvaluator } from './math-evaluator';
 | 
				
			||||||
 | 
					import { tool as jsonViewer } from './json-viewer';
 | 
				
			||||||
 | 
					import { tool as htmlEntities } from './html-entities';
 | 
				
			||||||
 | 
					import { tool as urlParser } from './url-parser';
 | 
				
			||||||
import { tool as deviceInformation } from './device-information';
 | 
					import { tool as deviceInformation } from './device-information';
 | 
				
			||||||
import { tool as bcrypt } from './bcrypt';
 | 
					import { tool as bcrypt } from './bcrypt';
 | 
				
			||||||
import { tool as caseConverter } from './case-converter';
 | 
					import { tool as caseConverter } from './case-converter';
 | 
				
			||||||
@@ -31,17 +35,29 @@ export const toolsByCategory: ToolCategory[] = [
 | 
				
			|||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'Converter',
 | 
					    name: 'Converter',
 | 
				
			||||||
    icon: LockOpen,
 | 
					    icon: LockOpen,
 | 
				
			||||||
    components: [dateTimeConverter, baseConverter, romanNumeralConverter, base64Converter, colorConverter, caseConverter],
 | 
					    components: [
 | 
				
			||||||
 | 
					      dateTimeConverter,
 | 
				
			||||||
 | 
					      baseConverter,
 | 
				
			||||||
 | 
					      romanNumeralConverter,
 | 
				
			||||||
 | 
					      base64Converter,
 | 
				
			||||||
 | 
					      colorConverter,
 | 
				
			||||||
 | 
					      caseConverter,
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'Web',
 | 
					    name: 'Web',
 | 
				
			||||||
    icon: LockOpen,
 | 
					    icon: LockOpen,
 | 
				
			||||||
    components: [urlEncoder, qrCodeGenerator, deviceInformation],
 | 
					    components: [urlEncoder, htmlEntities, qrCodeGenerator, urlParser, deviceInformation],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'Development',
 | 
					    name: 'Development',
 | 
				
			||||||
    icon: LockOpen,
 | 
					    icon: LockOpen,
 | 
				
			||||||
    components: [gitMemo, randomPortGenerator, crontabGenerator],
 | 
					    components: [gitMemo, randomPortGenerator, crontabGenerator, jsonViewer],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: 'Math',
 | 
				
			||||||
 | 
					    icon: LockOpen,
 | 
				
			||||||
 | 
					    components: [mathEvaluator],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: 'Text',
 | 
					    name: 'Text',
 | 
				
			||||||
@@ -51,4 +67,6 @@ export const toolsByCategory: ToolCategory[] = [
 | 
				
			|||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tools = toolsByCategory.flatMap(({ components }) => components);
 | 
					export const tools = toolsByCategory.flatMap(({ components }) => components);
 | 
				
			||||||
export const toolsWithCategory = toolsByCategory.flatMap(({ components, name }) => components.map((tool) => ({ category: name, ...tool })));
 | 
					export const toolsWithCategory = toolsByCategory.flatMap(({ components, name }) =>
 | 
				
			||||||
 | 
					  components.map((tool) => ({ category: name, ...tool })),
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { ArrowsLeftRight } from '@vicons/tabler';
 | 
					import { ArrowsLeftRight } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Integer base converter',
 | 
					  name: 'Integer base converter',
 | 
				
			||||||
  path: '/base-converter',
 | 
					  path: '/base-converter',
 | 
				
			||||||
  description: 'Convert numver between different bases (decimal, hexadecimal, binary, octale, base64, ...)',
 | 
					  description: 'Convert number between different bases (decimal, hexadecimal, binary, octal, base64, ...)',
 | 
				
			||||||
  keywords: ['integer', 'number', 'base', 'convertion', 'decimal', 'hexadecimal', 'binary', 'octale', 'base64'],
 | 
					  keywords: ['integer', 'number', 'base', 'conversion', 'decimal', 'hexadecimal', 'binary', 'octal', 'base64'],
 | 
				
			||||||
  component: () => import('./integer-base-converter.vue'),
 | 
					  component: () => import('./integer-base-converter.vue'),
 | 
				
			||||||
  icon: ArrowsLeftRight,
 | 
					  icon: ArrowsLeftRight,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,53 +1,37 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <n-card>
 | 
					    <n-card>
 | 
				
			||||||
 | 
					      <div v-if="styleStore.isSmallScreen">
 | 
				
			||||||
        <n-input-group>
 | 
					        <n-input-group>
 | 
				
			||||||
        <n-input-group-label style="width: 200px;">
 | 
					          <n-input-group-label style="flex: 0 0 120px"> Input number: </n-input-group-label>
 | 
				
			||||||
          Input number:
 | 
					          <n-input-number v-model:value="inputNumber" min="0" style="width: 100%" />
 | 
				
			||||||
        </n-input-group-label>
 | 
					        </n-input-group>
 | 
				
			||||||
        <n-input-number
 | 
					        <n-input-group>
 | 
				
			||||||
          v-model:value="inputNumber"
 | 
					          <n-input-group-label style="flex: 0 0 120px"> Input base: </n-input-group-label>
 | 
				
			||||||
          min="0"
 | 
					          <n-input-number v-model:value="inputBase" max="64" min="2" style="width: 100%" />
 | 
				
			||||||
        />
 | 
					        </n-input-group>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <n-input-group-label style="width: 200px;">
 | 
					      <n-input-group v-else>
 | 
				
			||||||
          Input base:
 | 
					        <n-input-group-label style="flex: 0 0 120px"> Input number: </n-input-group-label>
 | 
				
			||||||
        </n-input-group-label>
 | 
					        <n-input-number v-model:value="inputNumber" min="0" />
 | 
				
			||||||
        <n-input-number
 | 
					        <n-input-group-label style="flex: 0 0 120px"> Input base: </n-input-group-label>
 | 
				
			||||||
          v-model:value="inputBase"
 | 
					        <n-input-number v-model:value="inputBase" max="64" min="2" />
 | 
				
			||||||
          max="64"
 | 
					 | 
				
			||||||
          min="2"
 | 
					 | 
				
			||||||
          style="width: 100px;"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-input-group>
 | 
					      </n-input-group>
 | 
				
			||||||
      <n-divider />
 | 
					      <n-divider />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <n-input-group>
 | 
					      <n-input-group>
 | 
				
			||||||
        <n-input-group-label style="width: 200px;">
 | 
					        <n-input-group-label style="flex: 0 0 170px"> Binary (2): </n-input-group-label>
 | 
				
			||||||
          Binary (2):
 | 
					        <input-copyable :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 2 })" readonly />
 | 
				
			||||||
        </n-input-group-label>
 | 
					 | 
				
			||||||
        <input-copyable
 | 
					 | 
				
			||||||
          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 2 })"
 | 
					 | 
				
			||||||
          readonly
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-input-group>
 | 
					      </n-input-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <n-input-group>
 | 
					      <n-input-group>
 | 
				
			||||||
        <n-input-group-label style="width: 200px;">
 | 
					        <n-input-group-label style="flex: 0 0 170px"> Octal (8): </n-input-group-label>
 | 
				
			||||||
          Octale (8):
 | 
					        <input-copyable :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 8 })" readonly />
 | 
				
			||||||
        </n-input-group-label>
 | 
					 | 
				
			||||||
        <input-copyable
 | 
					 | 
				
			||||||
          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 8 })"
 | 
					 | 
				
			||||||
          readonly
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-input-group>
 | 
					      </n-input-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <n-input-group>
 | 
					      <n-input-group>
 | 
				
			||||||
        <n-input-group-label style="width: 200px;">
 | 
					        <n-input-group-label style="flex: 0 0 170px"> Decimal (10): </n-input-group-label>
 | 
				
			||||||
          Decimal (10):
 | 
					 | 
				
			||||||
        </n-input-group-label>
 | 
					 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable
 | 
				
			||||||
          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 10 })"
 | 
					          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 10 })"
 | 
				
			||||||
          readonly
 | 
					          readonly
 | 
				
			||||||
@@ -55,9 +39,7 @@
 | 
				
			|||||||
      </n-input-group>
 | 
					      </n-input-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <n-input-group>
 | 
					      <n-input-group>
 | 
				
			||||||
        <n-input-group-label style="width: 200px;">
 | 
					        <n-input-group-label style="flex: 0 0 170px"> Hexadecimal (16): </n-input-group-label>
 | 
				
			||||||
          Hexadecimal (16):
 | 
					 | 
				
			||||||
        </n-input-group-label>
 | 
					 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable
 | 
				
			||||||
          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 16 })"
 | 
					          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 16 })"
 | 
				
			||||||
          readonly
 | 
					          readonly
 | 
				
			||||||
@@ -65,24 +47,15 @@
 | 
				
			|||||||
      </n-input-group>
 | 
					      </n-input-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <n-input-group>
 | 
					      <n-input-group>
 | 
				
			||||||
        <n-input-group-label style="width: 200px;">
 | 
					        <n-input-group-label style="flex: 0 0 170px"> Base64 (64): </n-input-group-label>
 | 
				
			||||||
          Base64 (64):
 | 
					 | 
				
			||||||
        </n-input-group-label>
 | 
					 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable
 | 
				
			||||||
          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 64 })"
 | 
					          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: 64 })"
 | 
				
			||||||
          readonly
 | 
					          readonly
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </n-input-group>
 | 
					      </n-input-group>
 | 
				
			||||||
      <n-input-group>
 | 
					      <n-input-group>
 | 
				
			||||||
        <n-input-group-label style="width: 90px;">
 | 
					        <n-input-group-label style="flex: 0 0 85px"> Custom: </n-input-group-label>
 | 
				
			||||||
          Custom:
 | 
					        <n-input-number v-model:value="outputBase" style="flex: 0 0 86px" max="64" min="2" />
 | 
				
			||||||
        </n-input-group-label>
 | 
					 | 
				
			||||||
        <n-input-number
 | 
					 | 
				
			||||||
          v-model:value="outputBase"
 | 
					 | 
				
			||||||
          style="width: 110px;"
 | 
					 | 
				
			||||||
          max="64"
 | 
					 | 
				
			||||||
          min="2"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <input-copyable
 | 
					        <input-copyable
 | 
				
			||||||
          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: outputBase })"
 | 
					          :value="convertBase({ value: String(inputNumber), fromBase: inputBase, toBase: outputBase })"
 | 
				
			||||||
          readonly
 | 
					          readonly
 | 
				
			||||||
@@ -93,14 +66,16 @@
 | 
				
			|||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from 'vue'
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import { convertBase } from './integer-base-converter.model'
 | 
					import { convertBase } from './integer-base-converter.model';
 | 
				
			||||||
import InputCopyable from "../../components/InputCopyable.vue";
 | 
					import InputCopyable from '../../components/InputCopyable.vue';
 | 
				
			||||||
 | 
					import { useStyleStore } from '@/stores/style.store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inputNumber = ref(42)
 | 
					const styleStore = useStyleStore();
 | 
				
			||||||
const inputBase = ref(10)
 | 
					 | 
				
			||||||
const outputBase = ref(42)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const inputNumber = ref(42);
 | 
				
			||||||
 | 
					const inputBase = ref(10);
 | 
				
			||||||
 | 
					const outputBase = ref(42);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/tools/json-viewer/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/tools/json-viewer/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { Braces } from '@vicons/tabler';
 | 
				
			||||||
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const tool = defineTool({
 | 
				
			||||||
 | 
					  name: 'JSON viewer',
 | 
				
			||||||
 | 
					  path: '/json-viewer',
 | 
				
			||||||
 | 
					  description: 'Prettify JSON string to a human friendly readable format.',
 | 
				
			||||||
 | 
					  keywords: ['json', 'viewer', 'prettify', 'format'],
 | 
				
			||||||
 | 
					  component: () => import('./json-viewer.vue'),
 | 
				
			||||||
 | 
					  icon: Braces,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										66
									
								
								src/tools/json-viewer/json-viewer.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/tools/json-viewer/json-viewer.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <n-card>
 | 
				
			||||||
 | 
					    <n-form-item
 | 
				
			||||||
 | 
					      label="Your raw json:"
 | 
				
			||||||
 | 
					      :feedback="rawJsonValidation.message"
 | 
				
			||||||
 | 
					      :validation-status="rawJsonValidation.status"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <n-input
 | 
				
			||||||
 | 
					        v-model:value="rawJson"
 | 
				
			||||||
 | 
					        class="json-input"
 | 
				
			||||||
 | 
					        type="textarea"
 | 
				
			||||||
 | 
					        placeholder="Paste your raw json here..."
 | 
				
			||||||
 | 
					        autocomplete="off"
 | 
				
			||||||
 | 
					        autocorrect="off"
 | 
				
			||||||
 | 
					        autocapitalize="off"
 | 
				
			||||||
 | 
					        spellcheck="false"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-space justify="center">
 | 
				
			||||||
 | 
					      <n-button secondary @click="rawJson = ''">Clear</n-button>
 | 
				
			||||||
 | 
					    </n-space>
 | 
				
			||||||
 | 
					  </n-card>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <n-card v-if="cleanJson.length > 0">
 | 
				
			||||||
 | 
					    <n-scrollbar :x-scrollable="true">
 | 
				
			||||||
 | 
					      <n-config-provider :hljs="hljs">
 | 
				
			||||||
 | 
					        <n-code :code="cleanJson" language="json" />
 | 
				
			||||||
 | 
					      </n-config-provider>
 | 
				
			||||||
 | 
					    </n-scrollbar>
 | 
				
			||||||
 | 
					  </n-card>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { ref, computed } from 'vue';
 | 
				
			||||||
 | 
					import hljs from 'highlight.js/lib/core';
 | 
				
			||||||
 | 
					import json from 'highlight.js/lib/languages/json';
 | 
				
			||||||
 | 
					import { useValidation } from '@/composable/validation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					hljs.registerLanguage('json', json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rawJson = ref('');
 | 
				
			||||||
 | 
					const cleanJson = computed(() => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return JSON.stringify(JSON.parse(rawJson.value), null, 3);
 | 
				
			||||||
 | 
					  } catch (_) {
 | 
				
			||||||
 | 
					    return '';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rawJsonValidation = useValidation({
 | 
				
			||||||
 | 
					  source: rawJson,
 | 
				
			||||||
 | 
					  rules: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      validator: (v) => v === '' || JSON.parse(v),
 | 
				
			||||||
 | 
					      message: 'Invalid json string',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 | 
					.json-input ::v-deep(.n-input-wrapper) {
 | 
				
			||||||
 | 
					  resize: both !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
import { AlignJustified } from '@vicons/tabler';
 | 
					import { AlignJustified } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Lorem ipsum generator',
 | 
					  name: 'Lorem ipsum generator',
 | 
				
			||||||
  path: '/lorem-ipsum-generator',
 | 
					  path: '/lorem-ipsum-generator',
 | 
				
			||||||
  description: 'Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content',
 | 
					  description:
 | 
				
			||||||
 | 
					    'Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content',
 | 
				
			||||||
  keywords: ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'placeholder', 'text', 'filler', 'random', 'generator'],
 | 
					  keywords: ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'placeholder', 'text', 'filler', 'random', 'generator'],
 | 
				
			||||||
  component: () => import('./lorem-ipsum-generator.vue'),
 | 
					  component: () => import('./lorem-ipsum-generator.vue'),
 | 
				
			||||||
  icon: AlignJustified,
 | 
					  icon: AlignJustified,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -200,7 +200,9 @@ export function generateLoremIpsum({
 | 
				
			|||||||
  startWithLoremIpsum?: boolean;
 | 
					  startWithLoremIpsum?: boolean;
 | 
				
			||||||
  asHTML?: boolean;
 | 
					  asHTML?: boolean;
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const paragraphs = Array.from({ length: paragraphCount }).map(() => Array.from({ length: sentencePerParagraph }).map(() => generateSentence(wordCount)));
 | 
					  const paragraphs = Array.from({ length: paragraphCount }).map(() =>
 | 
				
			||||||
 | 
					    Array.from({ length: sentencePerParagraph }).map(() => generateSentence(wordCount)),
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (startWithLoremIpsum) {
 | 
					  if (startWithLoremIpsum) {
 | 
				
			||||||
    paragraphs[0][0] = firstSentence;
 | 
					    paragraphs[0][0] = firstSentence;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,106 +1,52 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card>
 | 
					  <n-card>
 | 
				
			||||||
    <n-form-item
 | 
					    <n-form-item label="Paragraphs" :show-feedback="false" label-width="200" label-placement="left">
 | 
				
			||||||
      label="Paragraphs"
 | 
					      <n-slider v-model:value="paragraphs" :step="1" :min="1" :max="20" />
 | 
				
			||||||
      :show-feedback="false"
 | 
					 | 
				
			||||||
      label-width="200"
 | 
					 | 
				
			||||||
      label-placement="left"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-slider
 | 
					 | 
				
			||||||
        v-model:value="paragraphs"
 | 
					 | 
				
			||||||
        :step="1"
 | 
					 | 
				
			||||||
        :min="1"
 | 
					 | 
				
			||||||
        :max="20"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
    <n-form-item
 | 
					    <n-form-item label="Sentences per paragraph" :show-feedback="false" label-width="200" label-placement="left">
 | 
				
			||||||
      label="Sentences per paragraph"
 | 
					      <n-slider v-model:value="sentences" range :step="1" :min="1" :max="50" />
 | 
				
			||||||
      :show-feedback="false"
 | 
					 | 
				
			||||||
      label-width="200"
 | 
					 | 
				
			||||||
      label-placement="left"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-slider
 | 
					 | 
				
			||||||
        v-model:value="sentences"
 | 
					 | 
				
			||||||
        range
 | 
					 | 
				
			||||||
        :step="1"
 | 
					 | 
				
			||||||
        :min="1"
 | 
					 | 
				
			||||||
        :max="50"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
    <n-form-item
 | 
					    <n-form-item label="Words per sentence" :show-feedback="false" label-width="200" label-placement="left">
 | 
				
			||||||
      label="Words per sentence"
 | 
					      <n-slider v-model:value="words" range :step="1" :min="1" :max="50" />
 | 
				
			||||||
      :show-feedback="false"
 | 
					 | 
				
			||||||
      label-width="200"
 | 
					 | 
				
			||||||
      label-placement="left"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-slider
 | 
					 | 
				
			||||||
        v-model:value="words"
 | 
					 | 
				
			||||||
        range
 | 
					 | 
				
			||||||
        :step="1"
 | 
					 | 
				
			||||||
        :min="1"
 | 
					 | 
				
			||||||
        :max="50"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
    <n-form-item
 | 
					    <n-form-item label="Start with lorem ipsum ?" :show-feedback="false" label-width="200" label-placement="left">
 | 
				
			||||||
      label="Start with lorem ipsum ?"
 | 
					 | 
				
			||||||
      :show-feedback="false"
 | 
					 | 
				
			||||||
      label-width="200"
 | 
					 | 
				
			||||||
      label-placement="left"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-switch v-model:value="startWithLoremIpsum" />
 | 
					      <n-switch v-model:value="startWithLoremIpsum" />
 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
    <n-form-item
 | 
					    <n-form-item label="As html ?" :show-feedback="false" label-width="200" label-placement="left">
 | 
				
			||||||
      label="As html ?"
 | 
					 | 
				
			||||||
      :show-feedback="false"
 | 
					 | 
				
			||||||
      label-width="200"
 | 
					 | 
				
			||||||
      label-placement="left"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-switch v-model:value="asHTML" />
 | 
					      <n-switch v-model:value="asHTML" />
 | 
				
			||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <br>
 | 
					    <br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <n-input
 | 
					    <n-input :value="loremIpsumText" type="textarea" placeholder="Your lorem ipsum..." autosize readonly />
 | 
				
			||||||
      :value="loremIpsumText"
 | 
					    <br />
 | 
				
			||||||
      type="textarea"
 | 
					    <br />
 | 
				
			||||||
      placeholder="Your lorem ipsum..."
 | 
					 | 
				
			||||||
      autosize
 | 
					 | 
				
			||||||
      readonly
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
      <n-button
 | 
					      <n-button secondary autofocus @click="copy"> Copy </n-button>
 | 
				
			||||||
        secondary
 | 
					 | 
				
			||||||
        autofocus
 | 
					 | 
				
			||||||
        @click="copy"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Copy
 | 
					 | 
				
			||||||
      </n-button>
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { ref, computed } from 'vue'
 | 
					import { ref, computed } from 'vue';
 | 
				
			||||||
import { generateLoremIpsum } from './lorem-ipsum-generator.service'
 | 
					import { generateLoremIpsum } from './lorem-ipsum-generator.service';
 | 
				
			||||||
import { randIntFromInterval } from '@/utils/random'
 | 
					import { randIntFromInterval } from '@/utils/random';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const paragraphs = ref(1)
 | 
					const paragraphs = ref(1);
 | 
				
			||||||
const sentences = ref([3, 8])
 | 
					const sentences = ref([3, 8]);
 | 
				
			||||||
const words = ref([8, 15])
 | 
					const words = ref([8, 15]);
 | 
				
			||||||
const startWithLoremIpsum = ref(true)
 | 
					const startWithLoremIpsum = ref(true);
 | 
				
			||||||
const asHTML = ref(false)
 | 
					const asHTML = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const loremIpsumText = computed(() => generateLoremIpsum({
 | 
					const loremIpsumText = computed(() =>
 | 
				
			||||||
 | 
					  generateLoremIpsum({
 | 
				
			||||||
    paragraphCount: paragraphs.value,
 | 
					    paragraphCount: paragraphs.value,
 | 
				
			||||||
    asHTML: asHTML.value,
 | 
					    asHTML: asHTML.value,
 | 
				
			||||||
    sentencePerParagraph: randIntFromInterval(sentences.value[0], sentences.value[1]),
 | 
					    sentencePerParagraph: randIntFromInterval(sentences.value[0], sentences.value[1]),
 | 
				
			||||||
    wordCount: randIntFromInterval(words.value[0], words.value[1]),
 | 
					    wordCount: randIntFromInterval(words.value[0], words.value[1]),
 | 
				
			||||||
    startWithLoremIpsum: startWithLoremIpsum.value
 | 
					    startWithLoremIpsum: startWithLoremIpsum.value,
 | 
				
			||||||
}))
 | 
					  }),
 | 
				
			||||||
const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to the clipboard' })
 | 
					);
 | 
				
			||||||
 | 
					const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to the clipboard' });
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								src/tools/math-evaluator/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/tools/math-evaluator/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					import { Math } from '@vicons/tabler';
 | 
				
			||||||
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const tool = defineTool({
 | 
				
			||||||
 | 
					  name: 'Math evaluator',
 | 
				
			||||||
 | 
					  path: '/math-evaluator',
 | 
				
			||||||
 | 
					  description: `Evaluate math expression, like a calculator on steroid (you can use function like sqrt, cos, sin, abs, ...)`,
 | 
				
			||||||
 | 
					  keywords: [
 | 
				
			||||||
 | 
					    'math',
 | 
				
			||||||
 | 
					    'evaluator',
 | 
				
			||||||
 | 
					    'acos',
 | 
				
			||||||
 | 
					    'acosh',
 | 
				
			||||||
 | 
					    'acot',
 | 
				
			||||||
 | 
					    'acoth',
 | 
				
			||||||
 | 
					    'acsc',
 | 
				
			||||||
 | 
					    'acsch',
 | 
				
			||||||
 | 
					    'asec',
 | 
				
			||||||
 | 
					    'asech',
 | 
				
			||||||
 | 
					    'asin',
 | 
				
			||||||
 | 
					    'asinh',
 | 
				
			||||||
 | 
					    'atan',
 | 
				
			||||||
 | 
					    'atan2',
 | 
				
			||||||
 | 
					    'atanh',
 | 
				
			||||||
 | 
					    'cos',
 | 
				
			||||||
 | 
					    'cosh',
 | 
				
			||||||
 | 
					    'cot',
 | 
				
			||||||
 | 
					    'coth',
 | 
				
			||||||
 | 
					    'csc',
 | 
				
			||||||
 | 
					    'csch',
 | 
				
			||||||
 | 
					    'sec',
 | 
				
			||||||
 | 
					    'sech',
 | 
				
			||||||
 | 
					    'sin',
 | 
				
			||||||
 | 
					    'sinh',
 | 
				
			||||||
 | 
					    'tan',
 | 
				
			||||||
 | 
					    'tanh',
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  component: () => import('./math-evaluator.vue'),
 | 
				
			||||||
 | 
					  icon: Math,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/tools/math-evaluator/math-evaluator.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/tools/math-evaluator/math-evaluator.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div>
 | 
				
			||||||
 | 
					    <n-input
 | 
				
			||||||
 | 
					      v-model:value="expression"
 | 
				
			||||||
 | 
					      rows="1"
 | 
				
			||||||
 | 
					      type="textarea"
 | 
				
			||||||
 | 
					      placeholder="Your math expression (ex: 2*sqrt(6) )..."
 | 
				
			||||||
 | 
					      size="large"
 | 
				
			||||||
 | 
					      autocomplete="off"
 | 
				
			||||||
 | 
					      autocorrect="off"
 | 
				
			||||||
 | 
					      autocapitalize="off"
 | 
				
			||||||
 | 
					      spellcheck="false"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <br />
 | 
				
			||||||
 | 
					    <br />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-card v-if="result !== ''" title="Result ">
 | 
				
			||||||
 | 
					      {{ result }}
 | 
				
			||||||
 | 
					    </n-card>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { evaluate } from 'mathjs';
 | 
				
			||||||
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const expression = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const result = computed(() => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return evaluate(expression.value) ?? '';
 | 
				
			||||||
 | 
					  } catch (_) {
 | 
				
			||||||
 | 
					    return '';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="less" scoped></style>
 | 
				
			||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
import { Qrcode } from '@vicons/tabler';
 | 
					import { Qrcode } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'QR Code generator',
 | 
					  name: 'QR Code generator',
 | 
				
			||||||
  path: '/qrcode-generator',
 | 
					  path: '/qrcode-generator',
 | 
				
			||||||
  description: 'Generate and download QR-code for an url or just a text and customize the background and foreground colors.',
 | 
					  description:
 | 
				
			||||||
 | 
					    'Generate and download QR-code for an url or just a text and customize the background and foreground colors.',
 | 
				
			||||||
  keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent'],
 | 
					  keywords: ['qr', 'code', 'generator', 'square', 'color', 'link', 'low', 'medium', 'quartile', 'high', 'transparent'],
 | 
				
			||||||
  component: () => import('./qr-code-generator.vue'),
 | 
					  component: () => import('./qr-code-generator.vue'),
 | 
				
			||||||
  icon: Qrcode,
 | 
					  icon: Qrcode,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,32 +1,16 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card>
 | 
					  <n-card>
 | 
				
			||||||
    <n-grid
 | 
					    <n-grid x-gap="12" y-gap="12" cols="1 600:3">
 | 
				
			||||||
      x-gap="12"
 | 
					 | 
				
			||||||
      y-gap="12"
 | 
					 | 
				
			||||||
      cols="1 600:3"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <n-gi span="2">
 | 
					      <n-gi span="2">
 | 
				
			||||||
        <n-form
 | 
					        <n-form label-width="130" label-placement="left">
 | 
				
			||||||
          label-width="130"
 | 
					 | 
				
			||||||
          label-placement="left"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <n-form-item label="Text:">
 | 
					          <n-form-item label="Text:">
 | 
				
			||||||
            <n-input
 | 
					            <n-input v-model:value="text" placeholder="Your link or text..." />
 | 
				
			||||||
              v-model:value="text"
 | 
					 | 
				
			||||||
              placeholder="Your link or text..."
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </n-form-item>
 | 
					          </n-form-item>
 | 
				
			||||||
          <n-form-item label="Foreground color:">
 | 
					          <n-form-item label="Foreground color:">
 | 
				
			||||||
            <n-color-picker
 | 
					            <n-color-picker v-model:value="foreground" :modes="['hex']" />
 | 
				
			||||||
              v-model:value="foreground"
 | 
					 | 
				
			||||||
              :modes="['hex']"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </n-form-item>
 | 
					          </n-form-item>
 | 
				
			||||||
          <n-form-item label="Background color:">
 | 
					          <n-form-item label="Background color:">
 | 
				
			||||||
            <n-color-picker
 | 
					            <n-color-picker v-model:value="background" :modes="['hex']" />
 | 
				
			||||||
              v-model:value="background"
 | 
					 | 
				
			||||||
              :modes="['hex']"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
          </n-form-item>
 | 
					          </n-form-item>
 | 
				
			||||||
          <n-form-item label="Error resistance:">
 | 
					          <n-form-item label="Error resistance:">
 | 
				
			||||||
            <n-select
 | 
					            <n-select
 | 
				
			||||||
@@ -37,21 +21,9 @@
 | 
				
			|||||||
        </n-form>
 | 
					        </n-form>
 | 
				
			||||||
      </n-gi>
 | 
					      </n-gi>
 | 
				
			||||||
      <n-gi>
 | 
					      <n-gi>
 | 
				
			||||||
        <n-space
 | 
					        <n-space justify="center" align="center" vertical>
 | 
				
			||||||
          justify="center"
 | 
					          <n-image :src="qrcode" width="200" />
 | 
				
			||||||
          align="center"
 | 
					          <n-button secondary @click="download"> Download qr-code </n-button>
 | 
				
			||||||
          vertical
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <n-image
 | 
					 | 
				
			||||||
            :src="qrcode"
 | 
					 | 
				
			||||||
            width="200"
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
          <n-button
 | 
					 | 
				
			||||||
            secondary
 | 
					 | 
				
			||||||
            @click="download"
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            Download qr-code
 | 
					 | 
				
			||||||
          </n-button>
 | 
					 | 
				
			||||||
        </n-space>
 | 
					        </n-space>
 | 
				
			||||||
      </n-gi>
 | 
					      </n-gi>
 | 
				
			||||||
    </n-grid>
 | 
					    </n-grid>
 | 
				
			||||||
@@ -60,28 +32,26 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
 | 
					import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
 | 
				
			||||||
import { useQRCode } from './useQRCode'
 | 
					import { useQRCode } from './useQRCode';
 | 
				
			||||||
import { ref } from 'vue';
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import type { QRCodeErrorCorrectionLevel } from 'qrcode';
 | 
					import type { QRCodeErrorCorrectionLevel } from 'qrcode';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const foreground = ref('#000000ff');
 | 
				
			||||||
 | 
					const background = ref('#ffffffff');
 | 
				
			||||||
 | 
					const errorCorrectionLevel = ref<QRCodeErrorCorrectionLevel>('medium');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const foreground = ref('#000000ff')
 | 
					const errorCorrectionLevels = ['low', 'medium', 'quartile', 'high'];
 | 
				
			||||||
const background = ref('#ffffffff')
 | 
					 | 
				
			||||||
const errorCorrectionLevel = ref<QRCodeErrorCorrectionLevel>('medium')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const errorCorrectionLevels = ['low', 'medium', 'quartile', 'high']
 | 
					const text = ref('https://it-tools.tech');
 | 
				
			||||||
 | 
					 | 
				
			||||||
const text = ref('https://it-tools.tech')
 | 
					 | 
				
			||||||
const { qrcode } = useQRCode({
 | 
					const { qrcode } = useQRCode({
 | 
				
			||||||
  text,
 | 
					  text,
 | 
				
			||||||
  color: {
 | 
					  color: {
 | 
				
			||||||
    background,
 | 
					    background,
 | 
				
			||||||
    foreground
 | 
					    foreground,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  errorCorrectionLevel,
 | 
					  errorCorrectionLevel,
 | 
				
			||||||
  options: { width: 1024 }
 | 
					  options: { width: 1024 },
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					 | 
				
			||||||
const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-code.png' })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { download } = useDownloadFileFromBase64({ source: qrcode, filename: 'qr-code.png' });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ export function useQRCode({
 | 
				
			|||||||
          ...options,
 | 
					          ...options,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    { immediate: true }
 | 
					    { immediate: true },
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return { qrcode };
 | 
					  return { qrcode };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { Server } from '@vicons/tabler';
 | 
					import { Server } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Random port generator',
 | 
					  name: 'Random port generator',
 | 
				
			||||||
  path: '/random-port-generator',
 | 
					  path: '/random-port-generator',
 | 
				
			||||||
  description: 'Generate random port numbers outside of the range of "known" ports (0-1023).',
 | 
					  description: 'Generate random port numbers outside of the range of "known" ports (0-1023).',
 | 
				
			||||||
  keywords: ['system', 'port', 'lan', 'generator', 'random', 'developement', 'computer'],
 | 
					  keywords: ['system', 'port', 'lan', 'generator', 'random', 'development', 'computer'],
 | 
				
			||||||
  component: () => import('./random-port-generator.vue'),
 | 
					  component: () => import('./random-port-generator.vue'),
 | 
				
			||||||
  icon: Server,
 | 
					  icon: Server,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,37 +4,26 @@
 | 
				
			|||||||
      {{ port }}
 | 
					      {{ port }}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
      <n-button
 | 
					      <n-button secondary @click="copy"> Copy </n-button>
 | 
				
			||||||
        secondary
 | 
					      <n-button secondary @click="refreshPort"> Refresh </n-button>
 | 
				
			||||||
        @click="copy"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Copy
 | 
					 | 
				
			||||||
      </n-button>
 | 
					 | 
				
			||||||
      <n-button
 | 
					 | 
				
			||||||
        secondary
 | 
					 | 
				
			||||||
        @click="refreshPort"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Refresh
 | 
					 | 
				
			||||||
      </n-button>
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { ref } from 'vue'
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import { generatePort } from './random-port-generator.model'
 | 
					import { generatePort } from './random-port-generator.model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const port = ref('')
 | 
					const port = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { copy } = useCopy({ source: port, text: 'Port copied to the clipboard' })
 | 
					const { copy } = useCopy({ source: port, text: 'Port copied to the clipboard' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function refreshPort() {
 | 
					function refreshPort() {
 | 
				
			||||||
    port.value = String(generatePort())
 | 
					  port.value = String(generatePort());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
refreshPort()
 | 
					refreshPort();
 | 
				
			||||||
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { LetterX } from '@vicons/tabler';
 | 
					import { LetterX } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Roman numeral converter',
 | 
					  name: 'Roman numeral converter',
 | 
				
			||||||
  path: '/roman-numeral-converter',
 | 
					  path: '/roman-numeral-converter',
 | 
				
			||||||
  description: 'Convert Roman numerals to numbers and convert numbers to Roman numerals.',
 | 
					  description: 'Convert Roman numerals to numbers and convert numbers to Roman numerals.',
 | 
				
			||||||
  keywords: ['roman', 'arabic', 'converter', 'X', 'I', 'V', 'L', 'C', 'D', 'M'],
 | 
					  keywords: ['roman', 'arabic', 'converter', 'X', 'I', 'V', 'L', 'C', 'D', 'M'],
 | 
				
			||||||
  component: () => import('./roman-numeral-converter.vue'),
 | 
					  component: () => import('./roman-numeral-converter.vue'),
 | 
				
			||||||
  icon: LetterX,
 | 
					  icon: LetterX,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,21 @@
 | 
				
			|||||||
export function arabicToRoman(num: number) {
 | 
					export function arabicToRoman(num: number) {
 | 
				
			||||||
  if (num < 1) return '';
 | 
					  if (num < 1) return '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const lookup: { [key: string]: number } = { M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1 };
 | 
					  const lookup: { [key: string]: number } = {
 | 
				
			||||||
 | 
					    M: 1000,
 | 
				
			||||||
 | 
					    CM: 900,
 | 
				
			||||||
 | 
					    D: 500,
 | 
				
			||||||
 | 
					    CD: 400,
 | 
				
			||||||
 | 
					    C: 100,
 | 
				
			||||||
 | 
					    XC: 90,
 | 
				
			||||||
 | 
					    L: 50,
 | 
				
			||||||
 | 
					    XL: 40,
 | 
				
			||||||
 | 
					    X: 10,
 | 
				
			||||||
 | 
					    IX: 9,
 | 
				
			||||||
 | 
					    V: 5,
 | 
				
			||||||
 | 
					    IV: 4,
 | 
				
			||||||
 | 
					    I: 1,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
  let roman = '';
 | 
					  let roman = '';
 | 
				
			||||||
  for (const i in lookup) {
 | 
					  for (const i in lookup) {
 | 
				
			||||||
    while (num >= lookup[i]) {
 | 
					    while (num >= lookup[i]) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,48 +1,22 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <n-card title="Arabic to roman">
 | 
					    <n-card title="Arabic to roman">
 | 
				
			||||||
      <n-space
 | 
					      <n-space align="center" justify="space-between">
 | 
				
			||||||
        align="center"
 | 
					        <n-input-number v-model:value="inputNumeral" :min="1" style="width: 200px" :show-button="false" />
 | 
				
			||||||
        justify="space-between"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-input-number
 | 
					 | 
				
			||||||
          v-model:value="inputNumeral"
 | 
					 | 
				
			||||||
          :min="1"
 | 
					 | 
				
			||||||
          style="width: 200px;"
 | 
					 | 
				
			||||||
          :show-button="false"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <div class="result">
 | 
					        <div class="result">
 | 
				
			||||||
          {{ outputRoman }}
 | 
					          {{ outputRoman }}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <n-button
 | 
					        <n-button secondary autofocus @click="copyRoman"> Copy </n-button>
 | 
				
			||||||
          secondary
 | 
					 | 
				
			||||||
          autofocus
 | 
					 | 
				
			||||||
          @click="copyRoman"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Copy
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
    </n-card>
 | 
					    </n-card>
 | 
				
			||||||
    <br>
 | 
					    <br />
 | 
				
			||||||
    <n-card title="Roman to arabic">
 | 
					    <n-card title="Roman to arabic">
 | 
				
			||||||
      <n-space
 | 
					      <n-space align="center" justify="space-between">
 | 
				
			||||||
        align="center"
 | 
					        <n-input v-model:value="inputRoman" style="width: 200px" />
 | 
				
			||||||
        justify="space-between"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-input
 | 
					 | 
				
			||||||
          v-model:value="inputRoman"
 | 
					 | 
				
			||||||
          style="width: 200px;"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <div class="result">
 | 
					        <div class="result">
 | 
				
			||||||
          {{ outputNumeral }}
 | 
					          {{ outputNumeral }}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <n-button
 | 
					        <n-button secondary autofocus @click="copyArabic"> Copy </n-button>
 | 
				
			||||||
          secondary
 | 
					 | 
				
			||||||
          autofocus
 | 
					 | 
				
			||||||
          @click="copyArabic"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Copy
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
    </n-card>
 | 
					    </n-card>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
@@ -50,18 +24,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { ref, computed } from 'vue'
 | 
					import { ref, computed } from 'vue';
 | 
				
			||||||
import { arabicToRoman, romanToArabic } from './roman-numeral-converter.service'
 | 
					import { arabicToRoman, romanToArabic } from './roman-numeral-converter.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inputNumeral = ref(42)
 | 
					const inputNumeral = ref(42);
 | 
				
			||||||
const outputRoman = computed(() => arabicToRoman(inputNumeral.value))
 | 
					const outputRoman = computed(() => arabicToRoman(inputNumeral.value));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inputRoman = ref('IVX')
 | 
					const inputRoman = ref('IVX');
 | 
				
			||||||
const outputNumeral = computed(() => romanToArabic(inputRoman.value))
 | 
					const outputNumeral = computed(() => romanToArabic(inputRoman.value));
 | 
				
			||||||
 | 
					 | 
				
			||||||
const { copy: copyRoman } = useCopy({ source: outputRoman, text: 'Roman number copied to the clipboard' })
 | 
					 | 
				
			||||||
const { copy: copyArabic } = useCopy({ source: outputNumeral, text: 'Arabic number copied to the clipboard' })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { copy: copyRoman } = useCopy({ source: outputRoman, text: 'Roman number copied to the clipboard' });
 | 
				
			||||||
 | 
					const { copy: copyArabic } = useCopy({ source: outputNumeral, text: 'Arabic number copied to the clipboard' });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="less" scoped>
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { FileText } from '@vicons/tabler';
 | 
					import { FileText } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Text statistics',
 | 
					  name: 'Text statistics',
 | 
				
			||||||
  path: '/text-statistics',
 | 
					  path: '/text-statistics',
 | 
				
			||||||
  description: "Get information about a text, the amount of characters, the amount of words, it's size, ...",
 | 
					  description: "Get information about a text, the amount of characters, the amount of words, it's size, ...",
 | 
				
			||||||
@@ -9,4 +9,4 @@ export const tool: ITool = {
 | 
				
			|||||||
  component: () => import('./text-statistics.vue'),
 | 
					  component: () => import('./text-statistics.vue'),
 | 
				
			||||||
  icon: FileText,
 | 
					  icon: FileText,
 | 
				
			||||||
  redirectFrom: ['/text-stats'],
 | 
					  redirectFrom: ['/text-stats'],
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,38 +1,23 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <n-card>
 | 
					  <n-card>
 | 
				
			||||||
    <n-input
 | 
					    <n-input v-model:value="text" type="textarea" placeholder="Your text..." rows="5" />
 | 
				
			||||||
      v-model:value="text"
 | 
					    <br />
 | 
				
			||||||
      type="textarea"
 | 
					    <br />
 | 
				
			||||||
      placeholder="Your text..."
 | 
					 | 
				
			||||||
      rows="5"
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
    <n-space justify="space-around">
 | 
					    <n-space justify="space-around">
 | 
				
			||||||
      <n-statistic
 | 
					      <n-statistic label="Character count" :value="text.length" />
 | 
				
			||||||
        label="Character count"
 | 
					      <n-statistic label="Word count" :value="text.split(/\s+/).length" />
 | 
				
			||||||
        :value="text.length"
 | 
					      <n-statistic label="Line count" :value="text.split(/\r\n|\r|\n/).length" />
 | 
				
			||||||
      />
 | 
					      <n-statistic label="Byte size" :value="formatBytes(getStringSizeInBytes(text))" />
 | 
				
			||||||
      <n-statistic
 | 
					 | 
				
			||||||
        label="Word count"
 | 
					 | 
				
			||||||
        :value="text.split(/\s+/).length"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <n-statistic
 | 
					 | 
				
			||||||
        label="Line count"
 | 
					 | 
				
			||||||
        :value="text.split(/\r\n|\r|\n/).length"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
      <n-statistic
 | 
					 | 
				
			||||||
        label="Byte size"
 | 
					 | 
				
			||||||
        :value="formatBytes(getStringSizeInBytes(text))"
 | 
					 | 
				
			||||||
      />
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { ref } from 'vue'
 | 
					import { ref } from 'vue';
 | 
				
			||||||
import { formatBytes } from '@/utils/convert'
 | 
					import { formatBytes } from '@/utils/convert';
 | 
				
			||||||
import { getStringSizeInBytes } from './text-statistics.service'
 | 
					import { getStringSizeInBytes } from './text-statistics.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const text = ref('Lorem ipsum dolor sit amet, consectetur adipiscing elit. Commodo risus faucibus varius volutpat habitasse suspendisse justo inceptos primis mi. Fusce molestie lorem bibendum habitasse litora adipiscing turpis egestas quis nec. Non id conubia vulputate etiam iaculis vitae venenatis hac fusce condimentum. Adipiscing pellentesque venenatis ornare pulvinar tempus hac montes velit erat convallis.')
 | 
					const text = ref(
 | 
				
			||||||
 | 
					  'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Commodo risus faucibus varius volutpat habitasse suspendisse justo inceptos primis mi. Fusce molestie lorem bibendum habitasse litora adipiscing turpis egestas quis nec. Non id conubia vulputate etiam iaculis vitae venenatis hac fusce condimentum. Adipiscing pellentesque venenatis ornare pulvinar tempus hac montes velit erat convallis.',
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,12 @@
 | 
				
			|||||||
import { ArrowsShuffle } from '@vicons/tabler';
 | 
					import { ArrowsShuffle } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from './../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Token generator',
 | 
					  name: 'Token generator',
 | 
				
			||||||
  path: '/token-generator',
 | 
					  path: '/token-generator',
 | 
				
			||||||
  description: 'Generate random string with the chars you want: uppercase or lowercase letters, numbers and/or symbols.',
 | 
					  description:
 | 
				
			||||||
 | 
					    'Generate random string with the chars you want: uppercase or lowercase letters, numbers and/or symbols.',
 | 
				
			||||||
  keywords: ['token', 'random', 'string', 'alphanumeric', 'symbols', 'number', 'letters', 'lowercase', 'uppercase'],
 | 
					  keywords: ['token', 'random', 'string', 'alphanumeric', 'symbols', 'number', 'letters', 'lowercase', 'uppercase'],
 | 
				
			||||||
  component: () => import('./token-generator.tool.vue'),
 | 
					  component: () => import('./token-generator.tool.vue'),
 | 
				
			||||||
  icon: ArrowsShuffle,
 | 
					  icon: ArrowsShuffle,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,8 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <n-card>
 | 
					    <n-card>
 | 
				
			||||||
      <n-form
 | 
					      <n-form label-placement="left" label-width="140">
 | 
				
			||||||
        label-placement="left"
 | 
					        <n-space justify="center" item-style="padding: 0" :size="0">
 | 
				
			||||||
        label-width="140"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-space
 | 
					 | 
				
			||||||
          justify="center"
 | 
					 | 
				
			||||||
          item-style="padding: 0"
 | 
					 | 
				
			||||||
          :size="0"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          <div>
 | 
					          <div>
 | 
				
			||||||
            <n-form-item label="Uppercase (ABC...)">
 | 
					            <n-form-item label="Uppercase (ABC...)">
 | 
				
			||||||
              <n-switch v-model:value="withUppercase" />
 | 
					              <n-switch v-model:value="withUppercase" />
 | 
				
			||||||
@@ -32,21 +25,13 @@
 | 
				
			|||||||
        </n-space>
 | 
					        </n-space>
 | 
				
			||||||
      </n-form>
 | 
					      </n-form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <n-form-item
 | 
					      <n-form-item :label="`Length (${length})`" label-placement="left">
 | 
				
			||||||
        :label="`Length (${length})`"
 | 
					        <n-slider v-model:value="length" :step="1" :min="1" :max="512" />
 | 
				
			||||||
        label-placement="left"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <n-slider
 | 
					 | 
				
			||||||
          v-model:value="length"
 | 
					 | 
				
			||||||
          :step="1"
 | 
					 | 
				
			||||||
          :min="1"
 | 
					 | 
				
			||||||
          :max="512"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-form-item>
 | 
					      </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <n-input
 | 
					      <n-input
 | 
				
			||||||
        v-model:value="token"
 | 
					        v-model:value="token"
 | 
				
			||||||
        style="text-align: center;"
 | 
					        style="text-align: center"
 | 
				
			||||||
        type="textarea"
 | 
					        type="textarea"
 | 
				
			||||||
        placeholder="The token..."
 | 
					        placeholder="The token..."
 | 
				
			||||||
        :autosize="{ minRows: 1 }"
 | 
					        :autosize="{ minRows: 1 }"
 | 
				
			||||||
@@ -56,22 +41,11 @@
 | 
				
			|||||||
        autocapitalize="off"
 | 
					        autocapitalize="off"
 | 
				
			||||||
        spellcheck="false"
 | 
					        spellcheck="false"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <br>
 | 
					      <br />
 | 
				
			||||||
      <br>
 | 
					      <br />
 | 
				
			||||||
      <n-space justify="center">
 | 
					      <n-space justify="center">
 | 
				
			||||||
        <n-button
 | 
					        <n-button secondary autofocus @click="copy"> Copy </n-button>
 | 
				
			||||||
          secondary
 | 
					        <n-button secondary @click="refreshToken"> Refresh </n-button>
 | 
				
			||||||
          autofocus
 | 
					 | 
				
			||||||
          @click="copy"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Copy
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
        <n-button
 | 
					 | 
				
			||||||
          secondary
 | 
					 | 
				
			||||||
          @click="refreshToken"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Refresh
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
    </n-card>
 | 
					    </n-card>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
@@ -82,17 +56,16 @@ import { useCopy } from '@/composable/copy';
 | 
				
			|||||||
import { ref, watch } from 'vue';
 | 
					import { ref, watch } from 'vue';
 | 
				
			||||||
import { createToken } from './token-generator.service';
 | 
					import { createToken } from './token-generator.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const token = ref('');
 | 
				
			||||||
 | 
					const length = ref(64);
 | 
				
			||||||
 | 
					const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const token = ref('')
 | 
					const withUppercase = ref(true);
 | 
				
			||||||
const length = ref(64)
 | 
					const withLowercase = ref(true);
 | 
				
			||||||
const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard' })
 | 
					const withNumbers = ref(true);
 | 
				
			||||||
 | 
					const withSymbols = ref(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const withUppercase = ref(true)
 | 
					watch([withUppercase, withLowercase, withNumbers, withSymbols, length], refreshToken);
 | 
				
			||||||
const withLowercase = ref(true)
 | 
					 | 
				
			||||||
const withNumbers = ref(true)
 | 
					 | 
				
			||||||
const withSymbols = ref(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
watch([withUppercase, withLowercase, withNumbers, withSymbols, length], refreshToken)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function refreshToken() {
 | 
					function refreshToken() {
 | 
				
			||||||
  token.value = createToken({
 | 
					  token.value = createToken({
 | 
				
			||||||
@@ -101,8 +74,8 @@ function refreshToken() {
 | 
				
			|||||||
    withLowercase: withLowercase.value,
 | 
					    withLowercase: withLowercase.value,
 | 
				
			||||||
    withNumbers: withNumbers.value,
 | 
					    withNumbers: withNumbers.value,
 | 
				
			||||||
    withSymbols: withSymbols.value,
 | 
					    withSymbols: withSymbols.value,
 | 
				
			||||||
    })
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
refreshToken()
 | 
					refreshToken();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/tools/tool.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/tools/tool.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import { config } from '@/config';
 | 
				
			||||||
 | 
					import type { Component } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ITool {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  path: string;
 | 
				
			||||||
 | 
					  description: string;
 | 
				
			||||||
 | 
					  keywords: string[];
 | 
				
			||||||
 | 
					  component: () => Promise<Component>;
 | 
				
			||||||
 | 
					  icon: Component;
 | 
				
			||||||
 | 
					  redirectFrom?: string[];
 | 
				
			||||||
 | 
					  isNew: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ToolCategory {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					  icon: Component;
 | 
				
			||||||
 | 
					  components: ITool[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function defineTool(
 | 
				
			||||||
 | 
					  tool: WithOptional<ITool, 'isNew'>,
 | 
				
			||||||
 | 
					  { newTools }: { newTools: string[] } = { newTools: config.tools.newTools },
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					  const isNew = newTools.includes(tool.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    isNew,
 | 
				
			||||||
 | 
					    ...tool,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import { Link } from '@vicons/tabler';
 | 
					import { Link } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'Encode/decode url formatted strings',
 | 
					  name: 'Encode/decode url formatted strings',
 | 
				
			||||||
  path: '/url-encoder',
 | 
					  path: '/url-encoder',
 | 
				
			||||||
  description: 'Encode to url-encoded format (also known as "percent-encoded") or decode from it.',
 | 
					  description: 'Encode to url-encoded format (also known as "percent-encoded") or decode from it.',
 | 
				
			||||||
  keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'],
 | 
					  keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'],
 | 
				
			||||||
  component: () => import('./url-encoder.vue'),
 | 
					  component: () => import('./url-encoder.vue'),
 | 
				
			||||||
  icon: Link,
 | 
					  icon: Link,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					 | 
				
			||||||
  <n-card title="Encode">
 | 
					  <n-card title="Encode">
 | 
				
			||||||
    <n-form-item
 | 
					    <n-form-item
 | 
				
			||||||
      label="Your string :"
 | 
					      label="Your string :"
 | 
				
			||||||
@@ -25,15 +24,9 @@
 | 
				
			|||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
        <n-button
 | 
					      <n-button secondary @click="copyEncoded"> Copy </n-button>
 | 
				
			||||||
          secondary
 | 
					 | 
				
			||||||
          @click="copyEncoded"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Copy
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
    <br>
 | 
					 | 
				
			||||||
  <n-card title="Decode">
 | 
					  <n-card title="Decode">
 | 
				
			||||||
    <n-form-item
 | 
					    <n-form-item
 | 
				
			||||||
      label="Your encoded string :"
 | 
					      label="Your encoded string :"
 | 
				
			||||||
@@ -59,74 +52,70 @@
 | 
				
			|||||||
    </n-form-item>
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <n-space justify="center">
 | 
					    <n-space justify="center">
 | 
				
			||||||
        <n-button
 | 
					      <n-button secondary @click="copyDecoded"> Copy </n-button>
 | 
				
			||||||
          secondary
 | 
					 | 
				
			||||||
          @click="copyDecoded"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Copy
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
    </n-space>
 | 
					    </n-space>
 | 
				
			||||||
  </n-card>
 | 
					  </n-card>
 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { useValidation } from '@/composable/validation';
 | 
					import { useValidation } from '@/composable/validation';
 | 
				
			||||||
import { computed, ref } from 'vue'
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const encodeInput = ref('Hello world :)')
 | 
					const encodeInput = ref('Hello world :)');
 | 
				
			||||||
const encodeOutput = computed(() => {
 | 
					const encodeOutput = computed(() => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    return encodeURIComponent(encodeInput.value)
 | 
					    return encodeURIComponent(encodeInput.value);
 | 
				
			||||||
  } catch (_) {
 | 
					  } catch (_) {
 | 
				
			||||||
    return ''
 | 
					    return '';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const encodedValidation = useValidation({
 | 
					const encodedValidation = useValidation({
 | 
				
			||||||
  source: encodeInput, rules: [{
 | 
					  source: encodeInput,
 | 
				
			||||||
 | 
					  rules: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
      validator: (value) => {
 | 
					      validator: (value) => {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
        encodeURIComponent(value)
 | 
					          encodeURIComponent(value);
 | 
				
			||||||
        return true
 | 
					          return true;
 | 
				
			||||||
        } catch (_) {
 | 
					        } catch (_) {
 | 
				
			||||||
        return false
 | 
					          return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    message: 'Impossible to parse this string'
 | 
					      message: 'Impossible to parse this string',
 | 
				
			||||||
  }]
 | 
					    },
 | 
				
			||||||
})
 | 
					  ],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { copy: copyEncoded } = useCopy({ source: encodeOutput, text: 'Encoded string copied to the clipboard' })
 | 
					const { copy: copyEncoded } = useCopy({ source: encodeOutput, text: 'Encoded string copied to the clipboard' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const decodeInput = ref('Hello%20world%20%3A)');
 | 
				
			||||||
const decodeInput = ref('Hello%20world%20%3A)')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const decodeOutput = computed(() => {
 | 
					const decodeOutput = computed(() => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    return decodeURIComponent(decodeInput.value)
 | 
					    return decodeURIComponent(decodeInput.value);
 | 
				
			||||||
  } catch (_) {
 | 
					  } catch (_) {
 | 
				
			||||||
    return ''
 | 
					    return '';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
})
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const decodeValidation = useValidation({
 | 
					const decodeValidation = useValidation({
 | 
				
			||||||
  source: encodeInput, rules: [{
 | 
					  source: encodeInput,
 | 
				
			||||||
 | 
					  rules: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
      validator: (value) => {
 | 
					      validator: (value) => {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
        decodeURIComponent(value)
 | 
					          decodeURIComponent(value);
 | 
				
			||||||
        return true
 | 
					          return true;
 | 
				
			||||||
        } catch (_) {
 | 
					        } catch (_) {
 | 
				
			||||||
        return false
 | 
					          return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    message: 'Impossible to parse this string'
 | 
					      message: 'Impossible to parse this string',
 | 
				
			||||||
  }]
 | 
					    },
 | 
				
			||||||
})
 | 
					  ],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
const { copy: copyDecoded } = useCopy({ source: decodeOutput, text: 'Decoded string copied to the clipboard' })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { copy: copyDecoded } = useCopy({ source: decodeOutput, text: 'Decoded string copied to the clipboard' });
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								src/tools/url-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/url-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import { Unlink } from '@vicons/tabler';
 | 
				
			||||||
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const tool = defineTool({
 | 
				
			||||||
 | 
					  name: 'Url parser',
 | 
				
			||||||
 | 
					  path: '/url-parser',
 | 
				
			||||||
 | 
					  description:
 | 
				
			||||||
 | 
					    'Parse an url string to get all the different parts (protocol, origin, params, port, username-password, ...)',
 | 
				
			||||||
 | 
					  keywords: ['url', 'parser', 'protocol', 'origin', 'params', 'port', 'username', 'password', 'href'],
 | 
				
			||||||
 | 
					  component: () => import('./url-parser.vue'),
 | 
				
			||||||
 | 
					  icon: Unlink,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										78
									
								
								src/tools/url-parser/url-parser.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/tools/url-parser/url-parser.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <n-card>
 | 
				
			||||||
 | 
					    <n-form-item label="Your url to parse:" :feedback="validation.message" :validation-status="validation.status">
 | 
				
			||||||
 | 
					      <n-input v-model:value="urlToParse" placeholder="Your url to parse..." />
 | 
				
			||||||
 | 
					    </n-form-item>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-divider style="margin-top: 0" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <n-form>
 | 
				
			||||||
 | 
					      <n-input-group v-for="{ title, key } in properties" :key="key">
 | 
				
			||||||
 | 
					        <n-input-group-label style="flex: 0 0 120px"> {{ title }}: </n-input-group-label>
 | 
				
			||||||
 | 
					        <input-copyable :value="(urlParsed?.[key] as string) ?? ''" readonly placeholder=" " />
 | 
				
			||||||
 | 
					      </n-input-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <n-input-group
 | 
				
			||||||
 | 
					        v-for="[k, v] in Object.entries(Object.fromEntries(urlParsed?.searchParams.entries() ?? []))"
 | 
				
			||||||
 | 
					        :key="k"
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <n-input-group-label style="flex: 0 0 120px">
 | 
				
			||||||
 | 
					          <n-icon :component="SubdirectoryArrowRightRound" />
 | 
				
			||||||
 | 
					        </n-input-group-label>
 | 
				
			||||||
 | 
					        <input-copyable :value="k" readonly />
 | 
				
			||||||
 | 
					        <input-copyable :value="v" readonly />
 | 
				
			||||||
 | 
					      </n-input-group>
 | 
				
			||||||
 | 
					    </n-form>
 | 
				
			||||||
 | 
					  </n-card>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang="ts">
 | 
				
			||||||
 | 
					import { computed, ref } from 'vue';
 | 
				
			||||||
 | 
					import { SubdirectoryArrowRightRound } from '@vicons/material';
 | 
				
			||||||
 | 
					import InputCopyable from '../../components/InputCopyable.vue';
 | 
				
			||||||
 | 
					import { useValidation } from '@/composable/validation';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const urlToParse = ref('https://me:pwd@it-tools.tech:3000/url-parser?key1=value&key2=value2#the-hash');
 | 
				
			||||||
 | 
					const urlParsed = computed<URL | undefined>(() => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return new URL(urlToParse.value);
 | 
				
			||||||
 | 
					  } catch (_) {
 | 
				
			||||||
 | 
					    return undefined;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const validation = useValidation({
 | 
				
			||||||
 | 
					  source: urlToParse,
 | 
				
			||||||
 | 
					  rules: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      validator: (value) => {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          new URL(value);
 | 
				
			||||||
 | 
					          return true;
 | 
				
			||||||
 | 
					        } catch (_) {
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      message: 'Invalid url',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const properties: { title: string; key: keyof URL }[] = [
 | 
				
			||||||
 | 
					  { title: 'Protocol', key: 'protocol' },
 | 
				
			||||||
 | 
					  { title: 'Username', key: 'username' },
 | 
				
			||||||
 | 
					  { title: 'Password', key: 'password' },
 | 
				
			||||||
 | 
					  { title: 'Hostname', key: 'hostname' },
 | 
				
			||||||
 | 
					  { title: 'Port', key: 'port' },
 | 
				
			||||||
 | 
					  { title: 'Path', key: 'pathname' },
 | 
				
			||||||
 | 
					  { title: 'Params', key: 'search' },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="less" scoped>
 | 
				
			||||||
 | 
					.n-input-group-label {
 | 
				
			||||||
 | 
					  text-align: right;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.n-input-group {
 | 
				
			||||||
 | 
					  margin: 2px 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Fingerprint } from '@vicons/tabler';
 | 
					import { Fingerprint } from '@vicons/tabler';
 | 
				
			||||||
import type { ITool } from '../Tool';
 | 
					import { defineTool } from '../tool';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const tool: ITool = {
 | 
					export const tool = defineTool({
 | 
				
			||||||
  name: 'UUIDs v4 generator',
 | 
					  name: 'UUIDs v4 generator',
 | 
				
			||||||
  path: '/uuid-generator',
 | 
					  path: '/uuid-generator',
 | 
				
			||||||
  description:
 | 
					  description:
 | 
				
			||||||
@@ -9,4 +9,4 @@ export const tool: ITool = {
 | 
				
			|||||||
  keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
 | 
					  keywords: ['uuid', 'v4', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
 | 
				
			||||||
  component: () => import('./uuid-generator.vue'),
 | 
					  component: () => import('./uuid-generator.vue'),
 | 
				
			||||||
  icon: Fingerprint,
 | 
					  icon: Fingerprint,
 | 
				
			||||||
};
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,13 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <div>
 | 
				
			||||||
    <n-card>
 | 
					    <n-card>
 | 
				
			||||||
      <n-space
 | 
					      <n-space align="center" justify="center">
 | 
				
			||||||
        align="center"
 | 
					 | 
				
			||||||
        justify="center"
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        Quantity :
 | 
					        Quantity :
 | 
				
			||||||
        <n-input-number
 | 
					        <n-input-number v-model:value="count" :min="1" :max="50" />
 | 
				
			||||||
          v-model:value="count"
 | 
					 | 
				
			||||||
          :min="1"
 | 
					 | 
				
			||||||
          :max="50"
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
      <br>
 | 
					      <br />
 | 
				
			||||||
      <n-input
 | 
					      <n-input
 | 
				
			||||||
        style="text-align: center; font-family: monospace;"
 | 
					        style="text-align: center; font-family: monospace"
 | 
				
			||||||
        :value="uuids"
 | 
					        :value="uuids"
 | 
				
			||||||
        type="textarea"
 | 
					        type="textarea"
 | 
				
			||||||
        placeholder="Your uuids"
 | 
					        placeholder="Your uuids"
 | 
				
			||||||
@@ -25,22 +18,11 @@
 | 
				
			|||||||
        autocapitalize="off"
 | 
					        autocapitalize="off"
 | 
				
			||||||
        spellcheck="false"
 | 
					        spellcheck="false"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <br>
 | 
					      <br />
 | 
				
			||||||
      <br>
 | 
					      <br />
 | 
				
			||||||
      <n-space justify="center">
 | 
					      <n-space justify="center">
 | 
				
			||||||
        <n-button
 | 
					        <n-button secondary autofocus @click="copy"> Copy </n-button>
 | 
				
			||||||
          secondary
 | 
					        <n-button secondary @click="refreshUUIDs"> Refresh </n-button>
 | 
				
			||||||
          autofocus
 | 
					 | 
				
			||||||
          @click="copy"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Copy
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
        <n-button
 | 
					 | 
				
			||||||
          secondary
 | 
					 | 
				
			||||||
          @click="refreshUUIDs"
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          Refresh
 | 
					 | 
				
			||||||
        </n-button>
 | 
					 | 
				
			||||||
      </n-space>
 | 
					      </n-space>
 | 
				
			||||||
    </n-card>
 | 
					    </n-card>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
@@ -48,20 +30,20 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { useCopy } from '@/composable/copy';
 | 
					import { useCopy } from '@/composable/copy';
 | 
				
			||||||
import { ref, watch } from 'vue'
 | 
					import { ref, watch } from 'vue';
 | 
				
			||||||
import { v4 as generateUUID } from 'uuid';
 | 
					import { v4 as generateUUID } from 'uuid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const count = ref(1)
 | 
					const count = ref(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const uuids = ref('')
 | 
					const uuids = ref('');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function refreshUUIDs() {
 | 
					function refreshUUIDs() {
 | 
				
			||||||
    uuids.value = Array.from({ length: count.value }, () => generateUUID()).join('\n')
 | 
					  uuids.value = Array.from({ length: count.value }, () => generateUUID()).join('\n');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
watch([count], refreshUUIDs)
 | 
					watch([count], refreshUUIDs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { copy } = useCopy({ source: uuids, text: 'UUIDs copied to the clipboard' })
 | 
					const { copy } = useCopy({ source: uuids, text: 'UUIDs copied to the clipboard' });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
refreshUUIDs()
 | 
					refreshUUIDs();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,6 +61,5 @@ export default defineConfig({
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  define: {
 | 
					  define: {
 | 
				
			||||||
    'import.meta.env.PACKAGE_VERSION': JSON.stringify(process.env.npm_package_version),
 | 
					    'import.meta.env.PACKAGE_VERSION': JSON.stringify(process.env.npm_package_version),
 | 
				
			||||||
    'import.meta.env.GIT_SHORT_SHA': JSON.stringify((process?.env?.VITE_VERCEL_GIT_COMMIT_SHA ?? '').slice(0, 7)),
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user