mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-10-23 04:52:14 +00:00
Compare commits
1 Commits
v2.18.0
...
landing-pa
Author | SHA1 | Date | |
---|---|---|---|
|
0bd8fabb85 |
43
CHANGELOG.md
43
CHANGELOG.md
@@ -2,49 +2,6 @@
|
||||
|
||||
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.18.0](https://github.com/CorentinTh/it-tools/compare/v2.17.0...v2.18.0) (2023-02-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **new-tool:** json minify ([#265](https://github.com/CorentinTh/it-tools/issues/265)) ([f708f50](https://github.com/CorentinTh/it-tools/commit/f708f5091e2182fc88e7cf3e7d23b3d05edc04da))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **tools:** config in query params ([db817a2](https://github.com/CorentinTh/it-tools/commit/db817a2459e23bd096274a7f91815d613d5f7ff4))
|
||||
|
||||
## [2.17.0](https://github.com/CorentinTh/it-tools/compare/v2.16.0...v2.17.0) (2023-01-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **new-tool:** jwt parser ([#262](https://github.com/CorentinTh/it-tools/issues/262)) ([acc7f0a](https://github.com/CorentinTh/it-tools/commit/acc7f0a586c64500c5f720e70cdbccf9bffe76d9))
|
||||
* **new-tool:** temperature converter ([4607837](https://github.com/CorentinTh/it-tools/commit/4607837f9a398440e0098f2ba862e8d7422ce94f))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **jwt-parser:** simplified code ([f52f7a8](https://github.com/CorentinTh/it-tools/commit/f52f7a845c34ce7da57b11c17d261733be89554f))
|
||||
|
||||
## [2.16.0](https://github.com/CorentinTh/it-tools/compare/v2.15.0...v2.16.0) (2022-12-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **search-bar:** use cmd + k to focus on mac ([bf88836](https://github.com/CorentinTh/it-tools/commit/bf88836dbe4037019e9545deaae1db06e5768cfb))
|
||||
* **tool:** improved favorite tool management ([af075dc](https://github.com/CorentinTh/it-tools/commit/af075dccccec959a0863e6d11516206860bed91f))
|
||||
* **tools:** added favorite tool handling ([4cd809b](https://github.com/CorentinTh/it-tools/commit/4cd809bd0c94836532f58a2ec6aa131694cce10d))
|
||||
* **tracker:** added actions monitoring ([bfc2e24](https://github.com/CorentinTh/it-tools/commit/bfc2e24bbfc08f67ed9c9b1d93474029bc01dc8b))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **clean:** removed empty style tag ([cf723f1](https://github.com/CorentinTh/it-tools/commit/cf723f144ee865b6de7323d3be58eb7a9586fa56))
|
||||
* **clean:** removed unused import ([4087285](https://github.com/CorentinTh/it-tools/commit/40872859a580a20bb838b79db2b3c88c00995e37))
|
||||
* **menu:** improve support button ([679dd1c](https://github.com/CorentinTh/it-tools/commit/679dd1c1f6265227cc9db60c55d83f8eaf8f72b4))
|
||||
* **tracker:** better tracker injection ([def60e7](https://github.com/CorentinTh/it-tools/commit/def60e7248003e74ed67e9ff116b438bab410a92))
|
||||
|
||||
## [2.15.0](https://github.com/CorentinTh/it-tools/compare/v2.14.1...v2.15.0) (2022-12-16)
|
||||
|
||||
|
||||
|
48
README.md
48
README.md
@@ -10,53 +10,46 @@ You have an idea of a tool? Submit a [feature request](https://github.com/Corent
|
||||
|
||||
## Contribute
|
||||
|
||||
## Recommended IDE Setup
|
||||
### Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
### Node version
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
|
||||
|
||||
1. Disable the built-in TypeScript Extension
|
||||
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
|
||||
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
Ensure you have the correct node/npm version
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
nvm use
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
### Project Setup
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
npm install
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
#### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||
#### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
#### Run Unit Tests with [Vitest](https://vitest.dev/)
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
npm run test
|
||||
```
|
||||
|
||||
#### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Create a new tool
|
||||
@@ -75,9 +68,12 @@ Coded with ❤️ by [Corentin Thomasset](//corentin-thomasset.fr).
|
||||
|
||||
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
|
||||
|
||||
This project is under the [GNU GPLv3](LICENSE).
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "it-tools",
|
||||
"version": "2.18.0",
|
||||
"version": "2.15.0",
|
||||
"description": "Collection of handy online tools for developers, with great UX. ",
|
||||
"keywords": [
|
||||
"productivity",
|
||||
@@ -37,7 +37,6 @@
|
||||
"@vicons/tabler": "^0.12.0",
|
||||
"@vueuse/core": "^8.9.4",
|
||||
"@vueuse/head": "^0.7.13",
|
||||
"@vueuse/router": "^9.11.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"change-case": "^4.1.2",
|
||||
"colord": "^2.9.3",
|
||||
@@ -49,7 +48,6 @@
|
||||
"fuse.js": "^6.6.2",
|
||||
"highlight.js": "^11.6.0",
|
||||
"json5": "^2.2.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mathjs": "^10.6.4",
|
||||
"mime-types": "^2.1.35",
|
||||
@@ -59,7 +57,6 @@
|
||||
"qrcode": "^1.5.1",
|
||||
"randombytes": "^2.1.0",
|
||||
"sql-formatter": "^8.2.0",
|
||||
"ts-pattern": "^4.1.3",
|
||||
"uuid": "^8.3.2",
|
||||
"vue": "^3.2.45",
|
||||
"vue-router": "^4.1.6"
|
||||
|
36
pnpm-lock.yaml
generated
36
pnpm-lock.yaml
generated
@@ -24,7 +24,6 @@ specifiers:
|
||||
'@vue/tsconfig': ^0.1.3
|
||||
'@vueuse/core': ^8.9.4
|
||||
'@vueuse/head': ^0.7.13
|
||||
'@vueuse/router': ^9.11.0
|
||||
bcryptjs: ^2.4.3
|
||||
c8: ^7.12.0
|
||||
change-case: ^4.1.2
|
||||
@@ -43,7 +42,6 @@ specifiers:
|
||||
highlight.js: ^11.6.0
|
||||
jsdom: ^19.0.0
|
||||
json5: ^2.2.1
|
||||
jwt-decode: ^3.1.2
|
||||
less: ^4.1.3
|
||||
lodash: ^4.17.21
|
||||
mathjs: ^10.6.4
|
||||
@@ -57,7 +55,6 @@ specifiers:
|
||||
sql-formatter: ^8.2.0
|
||||
standard-version: ^9.5.0
|
||||
start-server-and-test: ^1.14.0
|
||||
ts-pattern: ^4.1.3
|
||||
typescript: ~4.5.5
|
||||
uuid: ^8.3.2
|
||||
vite: ^2.9.15
|
||||
@@ -77,7 +74,6 @@ dependencies:
|
||||
'@vicons/tabler': 0.12.0
|
||||
'@vueuse/core': 8.9.4_vue@3.2.45
|
||||
'@vueuse/head': 0.7.13_vue@3.2.45
|
||||
'@vueuse/router': 9.11.0_xsxatmlnmmg5bcuv3xdnj6fj7y
|
||||
bcryptjs: 2.4.3
|
||||
change-case: 4.1.2
|
||||
colord: 2.9.3
|
||||
@@ -89,7 +85,6 @@ dependencies:
|
||||
fuse.js: 6.6.2
|
||||
highlight.js: 11.6.0
|
||||
json5: 2.2.1
|
||||
jwt-decode: 3.1.2
|
||||
lodash: 4.17.21
|
||||
mathjs: 10.6.4
|
||||
mime-types: 2.1.35
|
||||
@@ -99,7 +94,6 @@ dependencies:
|
||||
qrcode: 1.5.1
|
||||
randombytes: 2.1.0
|
||||
sql-formatter: 8.2.0
|
||||
ts-pattern: 4.1.3
|
||||
uuid: 8.3.2
|
||||
vue: 3.2.45
|
||||
vue-router: 4.1.6_vue@3.2.45
|
||||
@@ -2176,19 +2170,6 @@ packages:
|
||||
resolution: {integrity: sha512-IwSfzH80bnJMzqhaapqJl9JRIiyQU0zsRGEgnxN6jhq7992cPUJIRfV+JHRIZXjYqbwt07E1gTEp0R0zPJ1aqw==}
|
||||
dev: false
|
||||
|
||||
/@vueuse/router/9.11.0_xsxatmlnmmg5bcuv3xdnj6fj7y:
|
||||
resolution: {integrity: sha512-AlaQzbUy3XeqeoIapQxLvJjPzbdiO/Dt1thQhebP4iFUjc61a7WrboULxNBY+jEH0XipwK/T/b9mBnmn/jEStA==}
|
||||
peerDependencies:
|
||||
vue-router: '>=4.0.0-rc.1'
|
||||
dependencies:
|
||||
'@vueuse/shared': 9.11.0_vue@3.2.45
|
||||
vue-demi: 0.13.11_vue@3.2.45
|
||||
vue-router: 4.1.6_vue@3.2.45
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared/8.9.4_vue@3.2.45:
|
||||
resolution: {integrity: sha512-wt+T30c4K6dGRMVqPddexEVLa28YwxW5OFIPmzUHICjphfAuBFTTdDoyqREZNDOFJZ44ARH1WWQNCUK8koJ+Ag==}
|
||||
peerDependencies:
|
||||
@@ -2204,15 +2185,6 @@ packages:
|
||||
vue-demi: 0.13.11_vue@3.2.45
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared/9.11.0_vue@3.2.45:
|
||||
resolution: {integrity: sha512-8lO7wD5abYxupKy2KynH1pSgP715ky6iCrWYb8aX2AuAVi9uHXj7qE1dw6BnmArSaLHci4x9iuzWPCpAzUkC/A==}
|
||||
dependencies:
|
||||
vue-demi: 0.13.11_vue@3.2.45
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared/9.5.0_vue@3.2.45:
|
||||
resolution: {integrity: sha512-HnnCWU1Vg9CVWRCcI8ohDKDRB2Sc4bTgT1XAIaoLSfVHHn+TKbrox6pd3klCSw4UDxkhDfOk8cAdcK+Z5KleCA==}
|
||||
dependencies:
|
||||
@@ -4866,10 +4838,6 @@ packages:
|
||||
promise: 7.3.1
|
||||
dev: true
|
||||
|
||||
/jwt-decode/3.1.2:
|
||||
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
|
||||
dev: false
|
||||
|
||||
/kind-of/6.0.3:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -6521,10 +6489,6 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/ts-pattern/4.1.3:
|
||||
resolution: {integrity: sha512-8beXMWTGEv1JfDjSxfNhe4uT5jKYdhmEUKzt4gZW9dmHlquq3b+IbEyA7vX9LjBfzHmvKnM4HiomAUCyaW2Pew==}
|
||||
dev: false
|
||||
|
||||
/tsconfig-paths/3.14.1:
|
||||
resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==}
|
||||
dependencies:
|
||||
|
@@ -7,7 +7,7 @@ import { layouts } from './layouts';
|
||||
import { useStyleStore } from './stores/style.store';
|
||||
|
||||
const route = useRoute();
|
||||
const layout = computed(() => route?.meta?.layout ?? layouts.base);
|
||||
const layout = computed(() => route?.meta?.layout ?? layouts.navbar);
|
||||
const styleStore = useStyleStore();
|
||||
|
||||
const theme = computed(() => (styleStore.isDarkTheme ? darkTheme : null));
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 275">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 275" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<linearGradient id="small-hero-gradient-1" x1="13.74" y1="183.7" x2="303.96" y2="45.59" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#25636c"/>
|
||||
@@ -20,4 +20,4 @@
|
||||
<path fill="url(#small-hero-gradient-1)" d="M0,0V212.5s62.5-12.5,150,25,150,0,150,0V0Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 922 B |
@@ -36,3 +36,5 @@ function toggleFavorite(event: MouseEvent) {
|
||||
toolStore.addToolToFavorites({ tool });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
@@ -39,13 +39,6 @@ const siderPosition = computed(() => (isSmallScreen.value ? 'absolute' : 'static
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content {
|
||||
// background-color: #f1f5f9;
|
||||
::v-deep(.n-layout-scroll-container) {
|
||||
padding: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.n-layout {
|
||||
height: 100vh;
|
||||
}
|
||||
|
@@ -1,24 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import { useFuzzySearch } from '@/composable/fuzzySearch';
|
||||
import { useTracker } from '@/modules/tracker/tracker.services';
|
||||
import { tools } from '@/tools';
|
||||
import type { Tool } from '@/tools/tools.types';
|
||||
import { SearchRound } from '@vicons/material';
|
||||
import { useMagicKeys, whenever } from '@vueuse/core';
|
||||
import type { NInput } from 'naive-ui';
|
||||
import { computed, h, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import SearchBarItem from './SearchBarItem.vue';
|
||||
|
||||
const toolToOption = (tool: Tool) => ({ label: tool.name, value: tool.path, tool });
|
||||
|
||||
const router = useRouter();
|
||||
const { tracker } = useTracker();
|
||||
|
||||
const queryString = ref('');
|
||||
const inputEl = ref<HTMLElement>();
|
||||
const displayDropDown = ref(true);
|
||||
const isMac = computed(() => window.navigator.userAgent.toLowerCase().includes('mac'));
|
||||
|
||||
const { searchResult } = useFuzzySearch({
|
||||
search: queryString,
|
||||
data: tools,
|
||||
options: { keys: [{ name: 'name', weight: 2 }, 'description', 'keywords'] },
|
||||
});
|
||||
|
||||
const toolToOption = (tool: Tool) => ({ label: tool.name, value: tool.path, tool });
|
||||
|
||||
const options = computed(() => {
|
||||
if (queryString.value === '') {
|
||||
@@ -28,11 +27,12 @@ const options = computed(() => {
|
||||
return searchResult.value.map(toolToOption);
|
||||
});
|
||||
|
||||
const { searchResult } = useFuzzySearch({
|
||||
search: queryString,
|
||||
data: tools,
|
||||
options: { keys: [{ name: 'name', weight: 2 }, 'description', 'keywords'] },
|
||||
});
|
||||
function onSelect(path: string) {
|
||||
router.push(path);
|
||||
queryString.value = '';
|
||||
}
|
||||
|
||||
const focusTarget = ref();
|
||||
|
||||
const keys = useMagicKeys({
|
||||
passive: false,
|
||||
@@ -40,40 +40,16 @@ const keys = useMagicKeys({
|
||||
if (e.ctrlKey && e.key === 'k' && e.type === 'keydown') {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (e.metaKey && e.key === 'k' && e.type === 'keydown') {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
whenever(keys.ctrl_k, claimFocus);
|
||||
whenever(keys.meta_k, claimFocus);
|
||||
whenever(keys.escape, releaseFocus);
|
||||
whenever(keys.ctrl_k, () => {
|
||||
focusTarget.value.focus();
|
||||
});
|
||||
|
||||
function renderOption({ tool }: { tool: Tool }) {
|
||||
return h(SearchBarItem, { tool });
|
||||
}
|
||||
|
||||
function onSelect(path: string) {
|
||||
router.push(path);
|
||||
queryString.value = '';
|
||||
}
|
||||
|
||||
function claimFocus() {
|
||||
displayDropDown.value = true;
|
||||
|
||||
inputEl.value?.focus();
|
||||
}
|
||||
|
||||
function releaseFocus() {
|
||||
displayDropDown.value = false;
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
tracker.trackEvent({ eventName: 'Search-bar focused' });
|
||||
displayDropDown.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -84,16 +60,14 @@ function onFocus() {
|
||||
:on-select="(value) => onSelect(String(value))"
|
||||
:render-label="renderOption"
|
||||
:default-value="'aa'"
|
||||
:get-show="() => displayDropDown"
|
||||
:on-focus="onFocus"
|
||||
@update:value="() => (displayDropDown = true)"
|
||||
:get-show="() => true"
|
||||
>
|
||||
<template #default="{ handleInput, handleBlur, handleFocus, value: slotValue }">
|
||||
<n-input
|
||||
ref="inputEl"
|
||||
ref="focusTarget"
|
||||
round
|
||||
clearable
|
||||
:placeholder="`Search a tool (use ${isMac ? 'Cmd' : 'Ctrl'} + K to focus)`"
|
||||
placeholder="Search a tool... [Ctrl + K]"
|
||||
:value="slotValue"
|
||||
:input-props="{ autocomplete: 'disabled' }"
|
||||
@input="handleInput"
|
||||
@@ -108,3 +82,5 @@ function onFocus() {
|
||||
</n-auto-complete>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -1,35 +0,0 @@
|
||||
import { useRouteQuery } from '@vueuse/router';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export { useQueryParam };
|
||||
|
||||
const transformers = {
|
||||
number: {
|
||||
fromQuery: (value: string) => Number(value),
|
||||
toQuery: (value: number) => String(value),
|
||||
},
|
||||
string: {
|
||||
fromQuery: (value: string) => value,
|
||||
toQuery: (value: string) => value,
|
||||
},
|
||||
boolean: {
|
||||
fromQuery: (value: string) => value.toLowerCase() === 'true',
|
||||
toQuery: (value: boolean) => (value ? 'true' : 'false'),
|
||||
},
|
||||
};
|
||||
|
||||
function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
|
||||
const type = typeof defaultValue;
|
||||
const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
|
||||
|
||||
const proxy = useRouteQuery(name, transformer.toQuery(defaultValue as never));
|
||||
|
||||
return computed<T>({
|
||||
get() {
|
||||
return transformer.fromQuery(proxy.value) as T;
|
||||
},
|
||||
set(value) {
|
||||
proxy.value = transformer.toQuery(value as never);
|
||||
},
|
||||
});
|
||||
}
|
@@ -9,7 +9,6 @@ import { config } from '@/config';
|
||||
import MenuIconItem from '@/components/MenuIconItem.vue';
|
||||
import type { Tool } from '@/tools/tools.types';
|
||||
import { useToolStore } from '@/tools/tools.store';
|
||||
import { useTracker } from '@/modules/tracker/tracker.services';
|
||||
import SearchBar from '../components/SearchBar.vue';
|
||||
import HeroGradient from '../assets/hero-gradient.svg?component';
|
||||
import MenuLayout from '../components/MenuLayout.vue';
|
||||
@@ -24,8 +23,6 @@ const commitSha = config.app.lastCommitSha.slice(0, 7);
|
||||
const makeLabel = (tool: Tool) => () => h(RouterLink, { to: tool.path }, { default: () => tool.name });
|
||||
const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool });
|
||||
|
||||
const { tracker } = useTracker();
|
||||
|
||||
const toolStore = useToolStore();
|
||||
|
||||
const menuOptions = computed<MenuGroupOption[]>(() =>
|
||||
@@ -115,60 +112,6 @@ const menuOptions = computed<MenuGroupOption[]>(() =>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="navigation">
|
||||
<n-button
|
||||
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
|
||||
circle
|
||||
quaternary
|
||||
aria-label="Toggle menu"
|
||||
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
|
||||
>
|
||||
<n-icon size="25" :component="Menu2" />
|
||||
</n-button>
|
||||
|
||||
<router-link to="/" #="{ navigate, href }" custom>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button
|
||||
tag="a"
|
||||
:href="href"
|
||||
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
|
||||
circle
|
||||
quaternary
|
||||
aria-label="Home"
|
||||
@click="navigate"
|
||||
>
|
||||
<n-icon size="25" :component="Home2" />
|
||||
</n-button>
|
||||
</template>
|
||||
Home
|
||||
</n-tooltip>
|
||||
</router-link>
|
||||
|
||||
<search-bar />
|
||||
|
||||
<navbar-buttons v-if="!styleStore.isSmallScreen" />
|
||||
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button
|
||||
round
|
||||
type="primary"
|
||||
tag="a"
|
||||
href="https://github.com/sponsors/CorentinTh"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
class="support-button"
|
||||
:bordered="false"
|
||||
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
|
||||
>
|
||||
Buy me a coffee
|
||||
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-left: 5px" />
|
||||
</n-button>
|
||||
</template>
|
||||
❤ Support IT Tools development !
|
||||
</n-tooltip>
|
||||
</div>
|
||||
<slot />
|
||||
</template>
|
||||
</menu-layout>
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import BaseLayout from './base.layout.vue';
|
||||
import NavbarLayout from './navbar.layout.vue';
|
||||
import ToolLayout from './tool.layout.vue';
|
||||
|
||||
export const layouts = {
|
||||
base: BaseLayout,
|
||||
toolLayout: ToolLayout,
|
||||
navbar: NavbarLayout,
|
||||
};
|
||||
|
174
src/layouts/navbar.layout.vue
Normal file
174
src/layouts/navbar.layout.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<script lang="ts" setup>
|
||||
import { NIcon, useThemeVars } from 'naive-ui';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { Heart, Menu2, Home2 } from '@vicons/tabler';
|
||||
import { useStyleStore } from '@/stores/style.store';
|
||||
import SearchBar from '../components/SearchBar.vue';
|
||||
import BaseLayout from './base.layout.vue';
|
||||
import NavbarButtons from '../components/NavbarButtons.vue';
|
||||
|
||||
const themeVars = useThemeVars();
|
||||
const styleStore = useStyleStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<base-layout class="base-layout">
|
||||
<div class="navigation">
|
||||
<n-button
|
||||
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
|
||||
circle
|
||||
quaternary
|
||||
aria-label="Toggle menu"
|
||||
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
|
||||
>
|
||||
<n-icon size="25" :component="Menu2" />
|
||||
</n-button>
|
||||
|
||||
<router-link to="/" #="{ navigate, href }" custom>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button
|
||||
tag="a"
|
||||
:href="href"
|
||||
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
|
||||
circle
|
||||
quaternary
|
||||
aria-label="Home"
|
||||
@click="navigate"
|
||||
>
|
||||
<n-icon size="25" :component="Home2" />
|
||||
</n-button>
|
||||
</template>
|
||||
Home
|
||||
</n-tooltip>
|
||||
</router-link>
|
||||
|
||||
<search-bar />
|
||||
|
||||
<navbar-buttons v-if="!styleStore.isSmallScreen" />
|
||||
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button
|
||||
round
|
||||
type="primary"
|
||||
tag="a"
|
||||
href="https://github.com/sponsors/CorentinTh"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
class="support-button"
|
||||
:bordered="false"
|
||||
>
|
||||
Buy me a coffee
|
||||
|
||||
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-left: 8px" size="20px" />
|
||||
</n-button>
|
||||
</template>
|
||||
❤ Support IT Tools development !
|
||||
</n-tooltip>
|
||||
</div>
|
||||
<slot />
|
||||
</base-layout>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// ::v-deep(.n-layout-scroll-container) {
|
||||
// @percent: 4%;
|
||||
// @position: 25px;
|
||||
// @size: 50px;
|
||||
// @color: #eeeeee25;
|
||||
// background-image: radial-gradient(@color @percent, transparent @percent),
|
||||
// radial-gradient(@color @percent, transparent @percent);
|
||||
// background-position: 0 0, @position @position;
|
||||
// background-size: @size @size;
|
||||
// }
|
||||
|
||||
::v-deep(.content .n-layout-scroll-container) {
|
||||
padding: 26px;
|
||||
}
|
||||
|
||||
.support-button {
|
||||
background: rgb(37, 99, 108);
|
||||
background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
|
||||
color: #fff;
|
||||
transition: all ease 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
color: #838587;
|
||||
margin-top: 20px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.sider-content {
|
||||
padding-top: 160px;
|
||||
padding-bottom: 200px;
|
||||
}
|
||||
|
||||
.hero-wrapper {
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
|
||||
.gradient {
|
||||
margin-top: -65px;
|
||||
}
|
||||
|
||||
.text-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
top: 16px;
|
||||
color: #fff;
|
||||
|
||||
.title {
|
||||
font-size: 25px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 50px;
|
||||
height: 2px;
|
||||
border-radius: 4px;
|
||||
background-color: v-bind('themeVars.primaryColor');
|
||||
margin: 0 auto 5px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ::v-deep(.n-menu-item-content-header) {
|
||||
// overflow: visible !important;
|
||||
// // overflow-x: hidden !important;
|
||||
// }
|
||||
|
||||
.navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
|
||||
& > *:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
// width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -3,11 +3,13 @@ import { useRoute } from 'vue-router';
|
||||
import { useHead } from '@vueuse/head';
|
||||
import type { HeadObject } from '@vueuse/head';
|
||||
import { computed } from 'vue';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import FavoriteButton from '@/components/FavoriteButton.vue';
|
||||
import type { Tool } from '@/tools/tools.types';
|
||||
import BaseLayout from './base.layout.vue';
|
||||
import NavbarLayout from './navbar.layout.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const theme = useThemeVars();
|
||||
|
||||
const head = computed<HeadObject>(() => ({
|
||||
title: `${route.meta.name} - IT Tools`,
|
||||
@@ -26,7 +28,7 @@ useHead(head);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<base-layout>
|
||||
<navbar-layout>
|
||||
<div class="tool-layout">
|
||||
<div class="tool-header">
|
||||
<n-space align="center" justify="space-between" :wrap="false">
|
||||
@@ -50,7 +52,7 @@ useHead(head);
|
||||
<div class="tool-content">
|
||||
<slot />
|
||||
</div>
|
||||
</base-layout>
|
||||
</navbar-layout>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
@@ -1,27 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import type Plausible from 'plausible-tracker';
|
||||
import { inject } from 'vue';
|
||||
|
||||
export { createTrackerService, useTracker };
|
||||
|
||||
function createTrackerService({ plausible }: { plausible: ReturnType<typeof Plausible> }) {
|
||||
return {
|
||||
trackEvent({ eventName }: { eventName: string }) {
|
||||
plausible.trackEvent(eventName);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function useTracker() {
|
||||
const plausible: ReturnType<typeof Plausible> | undefined = inject('plausible');
|
||||
|
||||
if (_.isNil(plausible)) {
|
||||
throw new Error('Plausible must be instantiated');
|
||||
}
|
||||
|
||||
const tracker = createTrackerService({ plausible });
|
||||
|
||||
return {
|
||||
tracker,
|
||||
};
|
||||
}
|
@@ -1,3 +0,0 @@
|
||||
import type { createTrackerService } from './tracker.services';
|
||||
|
||||
export type TrackerService = ReturnType<typeof createTrackerService>;
|
@@ -1,9 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useTracker } from '@/modules/tracker/tracker.services';
|
||||
import { useHead } from '@vueuse/head';
|
||||
|
||||
useHead({ title: 'About - IT Tools' });
|
||||
const { tracker } = useTracker();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -27,7 +25,6 @@ const { tracker } = useTracker();
|
||||
href="https://github.com/sponsors/CorentinTh"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
|
||||
>
|
||||
sponsoring me </n-button
|
||||
>.
|
||||
|
@@ -4,6 +4,7 @@ import { Heart } from '@vicons/tabler';
|
||||
import { useHead } from '@vueuse/head';
|
||||
import ColoredCard from '../components/ColoredCard.vue';
|
||||
import ToolCard from '../components/ToolCard.vue';
|
||||
import Hero from './home/components/hero.vue';
|
||||
|
||||
const toolStore = useToolStore();
|
||||
|
||||
@@ -12,31 +13,32 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
|
||||
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<div class="grid-wrapper">
|
||||
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
||||
<n-gi>
|
||||
<colored-card title="You like it-tools?" :icon="Heart">
|
||||
Give us a star on
|
||||
<a
|
||||
href="https://github.com/CorentinTh/it-tools"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
aria-label="IT-Tools' github repository"
|
||||
>github</a
|
||||
>
|
||||
or follow us on
|
||||
<a
|
||||
href="https://twitter.com/ittoolsdottech"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
aria-label="IT-Tools' twitter account"
|
||||
>twitter</a
|
||||
>! Thank you
|
||||
<n-icon :component="Heart" />
|
||||
</colored-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<!-- <n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
||||
<n-gi>
|
||||
<colored-card title="You like it-tools?" :icon="Heart">
|
||||
Give us a star on
|
||||
<a
|
||||
href="https://github.com/CorentinTh/it-tools"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
aria-label="IT-Tools' github repository"
|
||||
>github</a
|
||||
>
|
||||
or follow us on
|
||||
<a
|
||||
href="https://twitter.com/ittoolsdottech"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
aria-label="IT-Tools' twitter account"
|
||||
>twitter</a
|
||||
>! Thank you
|
||||
<n-icon :component="Heart" />
|
||||
</colored-card>
|
||||
</n-gi>
|
||||
</n-grid> -->
|
||||
|
||||
<hero />
|
||||
<div class="grid-wrapper">
|
||||
<transition name="height">
|
||||
<div v-if="toolStore.favoriteTools.length > 0">
|
||||
<n-h3>Your favorite tools</n-h3>
|
||||
@@ -70,16 +72,12 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.home-page {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
.n-h3 {
|
||||
margin-bottom: 10px;
|
||||
.grid-wrapper {
|
||||
padding: 26px;
|
||||
}
|
||||
|
||||
::v-deep(.n-grid) {
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.height-enter-active,
|
||||
|
110
src/pages/home/components/hero.vue
Normal file
110
src/pages/home/components/hero.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div class="hero">
|
||||
<!-- <img :src="HeroGradientUrl" alt="Hero background image" class="hero-background" /> -->
|
||||
<div class="background-wrapper" :style="{ backgroundImage: `url(${HeroGradientUrl})` }">
|
||||
<div class="navigation">
|
||||
<n-button
|
||||
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
|
||||
circle
|
||||
quaternary
|
||||
aria-label="Toggle menu"
|
||||
color="#fff"
|
||||
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
|
||||
>
|
||||
<n-icon size="25" :component="Menu2" />
|
||||
</n-button>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<navbar-buttons v-if="!styleStore.isSmallScreen" />
|
||||
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button
|
||||
round
|
||||
type="primary"
|
||||
tag="a"
|
||||
href="https://github.com/sponsors/CorentinTh"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
ghost
|
||||
color="#fff"
|
||||
>
|
||||
Buy me a coffee
|
||||
|
||||
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-left: 8px" size="20px" />
|
||||
</n-button>
|
||||
</template>
|
||||
❤ Support IT Tools development !
|
||||
</n-tooltip>
|
||||
</div>
|
||||
|
||||
<n-space justify="center" class="content" vertical>
|
||||
<n-h1> Hello, world! </n-h1>
|
||||
<div class="subtitle">
|
||||
Welcome to IT-Tools! The collection of handy online tool for devs. Find everything you need to work in IT!
|
||||
</div>
|
||||
<search-bar />
|
||||
</n-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HeroGradientUrl from '@/assets/hero-gradient.svg?url';
|
||||
import NavbarButtons from '@/components/NavbarButtons.vue';
|
||||
import SearchBar from '@/components/SearchBar.vue';
|
||||
import { useStyleStore } from '@/stores/style.store';
|
||||
import { Heart, Menu2 } from '@vicons/tabler';
|
||||
|
||||
const styleStore = useStyleStore();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.hero {
|
||||
position: relative;
|
||||
color: #fff !important;
|
||||
|
||||
.n-h1 {
|
||||
margin-bottom: 0;
|
||||
line-height: 1;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
opacity: 0.8;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.background-wrapper {
|
||||
// background: rgb(37, 99, 108);
|
||||
// background: linear-gradient(48deg, rgba(37, 99, 108, 1) 0%, rgba(59, 149, 111, 1) 60%, rgba(20, 160, 88, 1) 100%);
|
||||
|
||||
background-size: 100% 150%;
|
||||
background-position: bottom;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 50px 0 300px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
padding: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
|
||||
& > *:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
// width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -53,7 +53,6 @@ import {
|
||||
NTooltip,
|
||||
NUpload,
|
||||
NUploadDragger,
|
||||
NPopover,
|
||||
NCheckbox,
|
||||
} from 'naive-ui';
|
||||
|
||||
@@ -112,7 +111,6 @@ const components = [
|
||||
NIcon,
|
||||
NSwitch,
|
||||
NCollapseTransition,
|
||||
NPopover,
|
||||
];
|
||||
|
||||
export const naive = create({ components });
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { config } from '@/config';
|
||||
|
||||
import Plausible from 'plausible-tracker';
|
||||
import type { App } from 'vue';
|
||||
|
||||
@@ -8,6 +7,6 @@ export const plausible = {
|
||||
const plausible = Plausible(config.plausible);
|
||||
plausible.enableAutoPageviews();
|
||||
|
||||
app.provide('plausible', plausible);
|
||||
app.config.globalProperties.$plausible = plausible;
|
||||
},
|
||||
};
|
||||
|
@@ -24,6 +24,7 @@ const router = createRouter({
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomePage,
|
||||
meta: { layout: layouts.base },
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
|
@@ -53,3 +53,5 @@ const b64Validation = useValidation({
|
||||
rules: [{ message: 'Invalid base64 string', validator: (value) => isValidBase64(value.trim()) }],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -40,7 +40,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useQueryParam } from '@/composable/queryParams';
|
||||
import { enc, lib, MD5, RIPEMD160, SHA1, SHA224, SHA256, SHA3, SHA384, SHA512 } from 'crypto-js';
|
||||
import { ref } from 'vue';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
@@ -60,7 +59,7 @@ const algos = {
|
||||
type AlgoNames = keyof typeof algos;
|
||||
type Encoding = keyof typeof enc | 'Bin';
|
||||
const algoNames = Object.keys(algos) as AlgoNames[];
|
||||
const encoding = useQueryParam<Encoding>({ defaultValue: 'Hex', name: 'encoding' });
|
||||
const encoding = ref<Encoding>('Hex');
|
||||
const clearText = ref('');
|
||||
|
||||
function formatWithEncoding(words: lib.WordArray, encoding: Encoding) {
|
||||
|
@@ -94,3 +94,5 @@ const hmac = computed(() =>
|
||||
);
|
||||
const { copy } = useCopy({ source: hmac });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { LockOpen } from '@vicons/tabler';
|
||||
|
||||
import { tool as chmodCalculator } from './chmod-calculator';
|
||||
import { tool as mimeTypes } from './mime-types';
|
||||
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
|
||||
import { tool as base64FileConverter } from './base64-file-converter';
|
||||
import { tool as base64StringConverter } from './base64-string-converter';
|
||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||
import { tool as jsonMinify } from './json-minify';
|
||||
import { tool as bcrypt } from './bcrypt';
|
||||
import { tool as bip39 } from './bip39-generator';
|
||||
import { tool as caseConverter } from './case-converter';
|
||||
import { tool as chmodCalculator } from './chmod-calculator';
|
||||
import { tool as chronometer } from './chronometer';
|
||||
import { tool as colorConverter } from './color-converter';
|
||||
import { tool as crontabGenerator } from './crontab-generator';
|
||||
@@ -19,24 +22,20 @@ import { tool as hmacGenerator } from './hmac-generator';
|
||||
import { tool as htmlEntities } from './html-entities';
|
||||
import { tool as baseConverter } from './integer-base-converter';
|
||||
import { tool as jsonViewer } from './json-viewer';
|
||||
import { tool as jwtParser } from './jwt-parser';
|
||||
import { tool as loremIpsumGenerator } from './lorem-ipsum-generator';
|
||||
import { tool as mathEvaluator } from './math-evaluator';
|
||||
import { tool as metaTagGenerator } from './meta-tag-generator';
|
||||
import { tool as mimeTypes } from './mime-types';
|
||||
import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator';
|
||||
import { tool as qrCodeGenerator } from './qr-code-generator';
|
||||
import { tool as randomPortGenerator } from './random-port-generator';
|
||||
import { tool as romanNumeralConverter } from './roman-numeral-converter';
|
||||
import { tool as sqlPrettify } from './sql-prettify';
|
||||
import { tool as svgPlaceholderGenerator } from './svg-placeholder-generator';
|
||||
import { tool as temperatureConverter } from './temperature-converter';
|
||||
import { tool as textStatistics } from './text-statistics';
|
||||
import { tool as tokenGenerator } from './token-generator';
|
||||
import type { ToolCategory } from './tools.types';
|
||||
import { tool as urlEncoder } from './url-encoder';
|
||||
import { tool as urlParser } from './url-parser';
|
||||
import { tool as uuidGenerator } from './uuid-generator';
|
||||
import type { ToolCategory } from './tools.types';
|
||||
|
||||
export const toolsByCategory: ToolCategory[] = [
|
||||
{
|
||||
@@ -66,7 +65,6 @@ export const toolsByCategory: ToolCategory[] = [
|
||||
metaTagGenerator,
|
||||
otpCodeGeneratorAndValidator,
|
||||
mimeTypes,
|
||||
jwtParser,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -75,7 +73,7 @@ export const toolsByCategory: ToolCategory[] = [
|
||||
},
|
||||
{
|
||||
name: 'Development',
|
||||
components: [gitMemo, randomPortGenerator, crontabGenerator, jsonViewer, jsonMinify, sqlPrettify, chmodCalculator],
|
||||
components: [gitMemo, randomPortGenerator, crontabGenerator, jsonViewer, sqlPrettify, chmodCalculator],
|
||||
},
|
||||
{
|
||||
name: 'Math',
|
||||
@@ -83,7 +81,7 @@ export const toolsByCategory: ToolCategory[] = [
|
||||
},
|
||||
{
|
||||
name: 'Measurement',
|
||||
components: [chronometer, temperatureConverter],
|
||||
components: [chronometer],
|
||||
},
|
||||
{
|
||||
name: 'Text',
|
||||
|
@@ -1,11 +0,0 @@
|
||||
import { Braces } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'JSON minify',
|
||||
path: '/json-minify',
|
||||
description: 'Minify and compress your JSON by removing unnecessary white spaces.',
|
||||
keywords: ['json', 'minify', 'format'],
|
||||
component: () => import('./json-minify.vue'),
|
||||
icon: Braces,
|
||||
});
|
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<n-form-item
|
||||
label="Your raw json"
|
||||
:feedback="rawJsonValidation.message"
|
||||
:validation-status="rawJsonValidation.status"
|
||||
>
|
||||
<n-input
|
||||
ref="inputElement"
|
||||
v-model:value="rawJson"
|
||||
placeholder="Paste your raw json here..."
|
||||
type="textarea"
|
||||
rows="20"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="Minify version of your JSON">
|
||||
<textarea-copyable :value="cleanJson" language="json" :follow-height-of="inputElement" />
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TextareaCopyable from '@/components/TextareaCopyable.vue';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import JSON5 from 'json5';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const inputElement = ref<HTMLElement>();
|
||||
|
||||
const rawJson = ref('{\n\t"hello": [\n\t\t"world"\n\t]\n}');
|
||||
const cleanJson = computed(() => withDefaultOnError(() => JSON.stringify(JSON5.parse(rawJson.value), null, 0), ''));
|
||||
|
||||
const rawJsonValidation = useValidation({
|
||||
source: rawJson,
|
||||
rules: [
|
||||
{
|
||||
validator: (v) => v === '' || JSON5.parse(v),
|
||||
message: 'Provided JSON is not valid.',
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.result-card {
|
||||
position: relative;
|
||||
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,27 +0,0 @@
|
||||
import { Key } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'JWT parser',
|
||||
path: '/jwt-parser',
|
||||
description: 'Parse and decode your JSON Web Token (jwt) and display its content.',
|
||||
keywords: [
|
||||
'jwt',
|
||||
'parser',
|
||||
'decode',
|
||||
'typ',
|
||||
'alg',
|
||||
'iss',
|
||||
'sub',
|
||||
'aud',
|
||||
'exp',
|
||||
'nbf',
|
||||
'iat',
|
||||
'jti',
|
||||
'json',
|
||||
'web',
|
||||
'token',
|
||||
],
|
||||
component: () => import('./jwt-parser.vue'),
|
||||
icon: Key,
|
||||
});
|
@@ -1,92 +0,0 @@
|
||||
// From https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
|
||||
export const ALGORITHM_DESCRIPTIONS: { [k: string]: string } = {
|
||||
HS256: 'HMAC using SHA-256',
|
||||
HS384: 'HMAC using SHA-384',
|
||||
HS512: 'HMAC using SHA-512',
|
||||
RS256: 'RSASSA-PKCS1-v1_5 using SHA-256',
|
||||
RS384: 'RSASSA-PKCS1-v1_5 using SHA-384',
|
||||
RS512: 'RSASSA-PKCS1-v1_5 using SHA-512',
|
||||
ES256: 'ECDSA using P-256 and SHA-256',
|
||||
ES384: 'ECDSA using P-384 and SHA-384',
|
||||
ES512: 'ECDSA using P-521 and SHA-512',
|
||||
PS256: 'RSASSA-PSS using SHA-256 and MGF1 with SHA-256',
|
||||
PS384: 'RSASSA-PSS using SHA-384 and MGF1 with SHA-384',
|
||||
PS512: 'RSASSA-PSS using SHA-512 and MGF1 with SHA-512',
|
||||
none: 'No digital signature or MAC performed',
|
||||
};
|
||||
|
||||
// List extracted from IANA: https://www.iana.org/assignments/jwt/jwt.xhtml
|
||||
export const CLAIM_DESCRIPTIONS: Record<string, string> = {
|
||||
typ: 'Type',
|
||||
alg: 'Algorithm',
|
||||
iss: 'Issuer',
|
||||
sub: 'Subject',
|
||||
aud: 'Audience',
|
||||
exp: 'Expiration Time',
|
||||
nbf: 'Not Before',
|
||||
iat: 'Issued At',
|
||||
jti: 'JWT ID',
|
||||
name: 'Full name',
|
||||
given_name: 'Given name(s) or first name(s)',
|
||||
family_name: 'Surname(s) or last name(s)',
|
||||
middle_name: 'Middle name(s)',
|
||||
nickname: 'Casual name',
|
||||
preferred_username: 'Shorthand name by which the End-User wishes to be referred to',
|
||||
profile: 'Profile page URL',
|
||||
picture: 'Profile picture URL',
|
||||
website: 'Web page or blog URL',
|
||||
email: 'Preferred e-mail address',
|
||||
email_verified: 'True if the e-mail address has been verified; otherwise false',
|
||||
gender: 'Gender',
|
||||
birthdate: 'Birthday',
|
||||
zoneinfo: 'Time zone',
|
||||
locale: 'Locale',
|
||||
phone_number: 'Preferred telephone number',
|
||||
phone_number_verified: 'True if the phone number has been verified; otherwise false',
|
||||
address: 'Preferred postal address',
|
||||
updated_at: 'Time the information was last updated',
|
||||
azp: 'Authorized party - the party to which the ID Token was issued',
|
||||
nonce: 'Value used to associate a Client session with an ID Token',
|
||||
auth_time: 'Time when the authentication occurred',
|
||||
at_hash: 'Access Token hash value',
|
||||
c_hash: 'Code hash value',
|
||||
acr: 'Authentication Context Class Reference',
|
||||
amr: 'Authentication Methods References',
|
||||
sub_jwk: 'Public key used to check the signature of an ID Token',
|
||||
cnf: 'Confirmation',
|
||||
sip_from_tag: 'SIP From tag header field parameter value',
|
||||
sip_date: 'SIP Date header field value',
|
||||
sip_callid: 'SIP Call-Id header field value',
|
||||
sip_cseq_num: 'SIP CSeq numeric header field parameter value',
|
||||
sip_via_branch: 'SIP Via branch header field parameter value',
|
||||
orig: 'Originating Identity String',
|
||||
dest: 'Destination Identity String',
|
||||
mky: 'Media Key Fingerprint String',
|
||||
events: 'Security Events',
|
||||
toe: 'Time of Event',
|
||||
txn: 'Transaction Identifier',
|
||||
rph: 'Resource Priority Header Authorization',
|
||||
sid: 'Session ID',
|
||||
vot: 'Vector of Trust value',
|
||||
vtm: 'Vector of Trust trustmark URL',
|
||||
attest: 'Attestation level as defined in SHAKEN framework',
|
||||
origid: 'Originating Identifier as defined in SHAKEN framework',
|
||||
act: 'Actor',
|
||||
scope: 'Scope Values',
|
||||
client_id: 'Client Identifier',
|
||||
may_act: 'Authorized Actor - the party that is authorized to become the actor',
|
||||
jcard: 'jCard data',
|
||||
at_use_nbr: 'Number of API requests for which the access token can be used',
|
||||
div: 'Diverted Target of a Call',
|
||||
opt: 'Original PASSporT (in Full Form)',
|
||||
vc: 'Verifiable Credential as specified in the W3C Recommendation',
|
||||
vp: 'Verifiable Presentation as specified in the W3C Recommendation',
|
||||
sph: 'SIP Priority header field',
|
||||
ace_profile: 'ACE profile a token is supposed to be used with.',
|
||||
cnonce: 'Client nonce',
|
||||
exi: 'Expires in',
|
||||
roles: 'Roles',
|
||||
groups: 'Groups',
|
||||
entitlements: 'Entitlements',
|
||||
token_introspection: 'Token introspection response',
|
||||
};
|
@@ -1,46 +0,0 @@
|
||||
import jwtDecode, { type JwtHeader, type JwtPayload } from 'jwt-decode';
|
||||
import _ from 'lodash';
|
||||
import { match } from 'ts-pattern';
|
||||
import { ALGORITHM_DESCRIPTIONS, CLAIM_DESCRIPTIONS } from './jwt-parser.constants';
|
||||
|
||||
export { decodeJwt };
|
||||
|
||||
function decodeJwt({ jwt }: { jwt: string }) {
|
||||
const rawHeader = jwtDecode<JwtHeader>(jwt, { header: true });
|
||||
const rawPayload = jwtDecode<JwtPayload>(jwt);
|
||||
|
||||
const header = _.map(rawHeader, (value, claim) => parseClaims({ claim, value }));
|
||||
const payload = _.map(rawPayload, (value, claim) => parseClaims({ claim, value }));
|
||||
|
||||
return {
|
||||
header,
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
function parseClaims({ claim, value }: { claim: string; value: unknown }) {
|
||||
const claimDescription = CLAIM_DESCRIPTIONS[claim];
|
||||
const formattedValue = _.toString(value);
|
||||
const friendlyValue = getFriendlyValue({ claim, value });
|
||||
|
||||
return {
|
||||
value: formattedValue,
|
||||
friendlyValue,
|
||||
claim,
|
||||
claimDescription,
|
||||
};
|
||||
}
|
||||
|
||||
function getFriendlyValue({ claim, value }: { claim: string; value: unknown }) {
|
||||
return match(claim)
|
||||
.with('exp', 'nbf', 'iat', () => dateFormatter(value))
|
||||
.with('alg', () => (_.isString(value) ? ALGORITHM_DESCRIPTIONS[value] : undefined))
|
||||
.otherwise(() => undefined);
|
||||
}
|
||||
|
||||
const dateFormatter = (value: unknown) => {
|
||||
if (_.isNil(value)) return undefined;
|
||||
|
||||
const date = new Date(Number(value) * 1000);
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
||||
};
|
@@ -1,70 +0,0 @@
|
||||
<template>
|
||||
<n-card>
|
||||
<n-form-item label="JWT to decode" :feedback="validation.message" :validation-status="validation.status">
|
||||
<n-input v-model:value="rawJwt" type="textarea" placeholder="Put your token here..." rows="5" />
|
||||
</n-form-item>
|
||||
|
||||
<n-table v-if="validation.isValid">
|
||||
<tbody>
|
||||
<template v-for="section of sections" :key="section.key">
|
||||
<th colspan="2" class="table-header">{{ section.title }}</th>
|
||||
<tr v-for="{ claim, claimDescription, friendlyValue, value } in decodedJWT[section.key]" :key="claim + value">
|
||||
<td class="claims">
|
||||
<n-space>
|
||||
<n-text strong>{{ claim }}</n-text>
|
||||
<template v-if="claimDescription">
|
||||
<n-text depth="3">({{ claimDescription }})</n-text>
|
||||
</template>
|
||||
</n-space>
|
||||
</td>
|
||||
<td>
|
||||
<n-space>
|
||||
<n-text>{{ value }}</n-text>
|
||||
<template v-if="friendlyValue">
|
||||
<n-text depth="3">({{ friendlyValue }})</n-text>
|
||||
</template>
|
||||
</n-space>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { isNotThrowing } from '@/utils/boolean';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { computed, ref } from 'vue';
|
||||
import { decodeJwt } from './jwt-parser.service';
|
||||
|
||||
const rawJwt = ref(
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
|
||||
);
|
||||
|
||||
const decodedJWT = computed(() =>
|
||||
withDefaultOnError(() => decodeJwt({ jwt: rawJwt.value }), { header: [], payload: [] }),
|
||||
);
|
||||
|
||||
const sections = [
|
||||
{ key: 'header', title: 'Header' },
|
||||
{ key: 'payload', title: 'Payload' },
|
||||
] as const;
|
||||
|
||||
const validation = useValidation({
|
||||
source: rawJwt,
|
||||
rules: [
|
||||
{
|
||||
validator: (value) => value.length > 0 && isNotThrowing(() => decodeJwt({ jwt: rawJwt.value })),
|
||||
message: 'Invalid JWT',
|
||||
},
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table-header {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
@@ -29,3 +29,5 @@ const expression = ref('');
|
||||
|
||||
const result = computed(() => withDefaultOnError(() => evaluate(expression.value) ?? '', ''));
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -95,3 +95,5 @@ const selectedExtension = ref(undefined);
|
||||
|
||||
const mimeTypeFound = computed(() => (selectedExtension.value ? extensionToMimeType[selectedExtension.value] : []));
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
@@ -1,24 +0,0 @@
|
||||
import { Temperature } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Temperature converter',
|
||||
path: '/temperature-converter',
|
||||
description:
|
||||
'Temperature degrees conversions for Kelvin, Celsius, Fahrenheit, Rankine, Delisle, Newton, Réaumur and Rømer.',
|
||||
keywords: [
|
||||
'temperature',
|
||||
'converter',
|
||||
'degree',
|
||||
'Kelvin',
|
||||
'Celsius',
|
||||
'Fahrenheit',
|
||||
'Rankine',
|
||||
'Delisle',
|
||||
'Newton',
|
||||
'Réaumur',
|
||||
'Rømer',
|
||||
],
|
||||
component: () => import('./temperature-converter.vue'),
|
||||
icon: Temperature,
|
||||
});
|
@@ -1,20 +0,0 @@
|
||||
export const convertCelsiusToKelvin = (temperature: number) => temperature + 273.15;
|
||||
export const convertKelvinToCelsius = (temperature: number) => temperature - 273.15;
|
||||
|
||||
export const convertFahrenheitToKelvin = (temperature: number) => (temperature + 459.67) * (5 / 9);
|
||||
export const convertKelvinToFahrenheit = (temperature: number) => temperature * (9 / 5) - 459.67;
|
||||
|
||||
export const convertRankineToKelvin = (temperature: number) => temperature * (5 / 9);
|
||||
export const convertKelvinToRankine = (temperature: number) => temperature * (9 / 5);
|
||||
|
||||
export const convertDelisleToKelvin = (temperature: number) => 373.15 - (2 / 3) * temperature;
|
||||
export const convertKelvinToDelisle = (temperature: number) => (3 / 2) * (373.15 - temperature);
|
||||
|
||||
export const convertNewtonToKelvin = (temperature: number) => temperature * (100 / 33) + 273.15;
|
||||
export const convertKelvinToNewton = (temperature: number) => (temperature - 273.15) * (33 / 100);
|
||||
|
||||
export const convertReaumurToKelvin = (temperature: number) => temperature * (5 / 4) + 273.15;
|
||||
export const convertKelvinToReaumur = (temperature: number) => (temperature - 273.15) * (4 / 5);
|
||||
|
||||
export const convertRomerToKelvin = (temperature: number) => (temperature - 7.5) * (40 / 21) + 273.15;
|
||||
export const convertKelvinToRomer = (temperature: number) => (temperature - 273.15) * (21 / 40) + 7.5;
|
@@ -1,127 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-input-group
|
||||
v-for="[key, { title, unit }] in Object.entries(units)"
|
||||
:key="key"
|
||||
style="width: 100%; margin-bottom: 15px"
|
||||
>
|
||||
<n-input-group-label style="width: 100px">
|
||||
{{ title }}
|
||||
</n-input-group-label>
|
||||
|
||||
<n-input-number
|
||||
v-model:value="units[key].ref"
|
||||
style="flex: 1"
|
||||
@update:value="() => update(key as TemperatureScale)"
|
||||
/>
|
||||
|
||||
<n-input-group-label style="width: 50px">
|
||||
{{ unit }}
|
||||
</n-input-group-label>
|
||||
</n-input-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { reactive } from 'vue';
|
||||
import {
|
||||
convertCelsiusToKelvin,
|
||||
convertDelisleToKelvin,
|
||||
convertFahrenheitToKelvin,
|
||||
convertKelvinToCelsius,
|
||||
convertKelvinToDelisle,
|
||||
convertKelvinToFahrenheit,
|
||||
convertKelvinToNewton,
|
||||
convertKelvinToRankine,
|
||||
convertKelvinToReaumur,
|
||||
convertKelvinToRomer,
|
||||
convertNewtonToKelvin,
|
||||
convertRankineToKelvin,
|
||||
convertReaumurToKelvin,
|
||||
convertRomerToKelvin,
|
||||
} from './temperature-converter.models';
|
||||
|
||||
type TemperatureScale = 'kelvin' | 'celsius' | 'fahrenheit' | 'rankine' | 'delisle' | 'newton' | 'reaumur' | 'romer';
|
||||
|
||||
const units = reactive<
|
||||
Record<
|
||||
string | TemperatureScale,
|
||||
{ title: string; unit: string; ref: number; toKelvin: (v: number) => number; fromKelvin: (v: number) => number }
|
||||
>
|
||||
>({
|
||||
kelvin: {
|
||||
title: 'Kelvin',
|
||||
unit: 'K',
|
||||
ref: 0,
|
||||
toKelvin: _.identity,
|
||||
fromKelvin: _.identity,
|
||||
},
|
||||
celsius: {
|
||||
title: 'Celsius',
|
||||
unit: '°C',
|
||||
ref: 0,
|
||||
toKelvin: convertCelsiusToKelvin,
|
||||
fromKelvin: convertKelvinToCelsius,
|
||||
},
|
||||
fahrenheit: {
|
||||
title: 'Fahrenheit',
|
||||
unit: '°F',
|
||||
ref: 0,
|
||||
toKelvin: convertFahrenheitToKelvin,
|
||||
fromKelvin: convertKelvinToFahrenheit,
|
||||
},
|
||||
rankine: {
|
||||
title: 'Rankine',
|
||||
unit: '°R',
|
||||
ref: 0,
|
||||
toKelvin: convertRankineToKelvin,
|
||||
fromKelvin: convertKelvinToRankine,
|
||||
},
|
||||
delisle: {
|
||||
title: 'Delisle',
|
||||
unit: '°De',
|
||||
ref: 0,
|
||||
toKelvin: convertDelisleToKelvin,
|
||||
fromKelvin: convertKelvinToDelisle,
|
||||
},
|
||||
newton: {
|
||||
title: 'Newton',
|
||||
unit: '°N',
|
||||
ref: 0,
|
||||
toKelvin: convertNewtonToKelvin,
|
||||
fromKelvin: convertKelvinToNewton,
|
||||
},
|
||||
reaumur: {
|
||||
title: 'Réaumur',
|
||||
unit: '°Ré',
|
||||
ref: 0,
|
||||
toKelvin: convertReaumurToKelvin,
|
||||
fromKelvin: convertKelvinToReaumur,
|
||||
},
|
||||
romer: {
|
||||
title: 'Rømer',
|
||||
unit: '°Rø',
|
||||
ref: 0,
|
||||
toKelvin: convertRomerToKelvin,
|
||||
fromKelvin: convertKelvinToRomer,
|
||||
},
|
||||
});
|
||||
|
||||
function update(key: TemperatureScale) {
|
||||
const { ref: value, toKelvin } = units[key];
|
||||
|
||||
const kelvins = toKelvin(value) ?? 0;
|
||||
|
||||
_.chain(units)
|
||||
.omit(key)
|
||||
.forEach(({ fromKelvin }, index) => {
|
||||
units[index].ref = Math.floor((fromKelvin(kelvins) ?? 0) * 100) / 100;
|
||||
})
|
||||
.value();
|
||||
}
|
||||
|
||||
update('kelvin');
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
@@ -54,19 +54,18 @@
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useQueryParam } from '@/composable/queryParams';
|
||||
import { createToken } from './token-generator.service';
|
||||
|
||||
const token = ref('');
|
||||
const length = useQueryParam({ name: 'length', defaultValue: 64 });
|
||||
const length = ref(64);
|
||||
const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard' });
|
||||
|
||||
const withUppercase = useQueryParam({ name: 'uppercase', defaultValue: true });
|
||||
const withLowercase = useQueryParam({ name: 'lowercase', defaultValue: true });
|
||||
const withNumbers = useQueryParam({ name: 'numbers', defaultValue: true });
|
||||
const withSymbols = useQueryParam({ name: 'symbols', defaultValue: false });
|
||||
const withUppercase = ref(true);
|
||||
const withLowercase = ref(true);
|
||||
const withNumbers = ref(true);
|
||||
const withSymbols = ref(false);
|
||||
|
||||
watch([withUppercase, withLowercase, withNumbers, withSymbols, length], refreshToken, { immediate: true });
|
||||
watch([withUppercase, withLowercase, withNumbers, withSymbols, length], refreshToken);
|
||||
|
||||
function refreshToken() {
|
||||
token.value = createToken({
|
||||
@@ -77,4 +76,6 @@ function refreshToken() {
|
||||
withSymbols: withSymbols.value,
|
||||
});
|
||||
}
|
||||
|
||||
refreshToken();
|
||||
</script>
|
||||
|
@@ -32,9 +32,8 @@
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { ref, watch } from 'vue';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import { useQueryParam } from '@/composable/queryParams';
|
||||
|
||||
const count = useQueryParam({ defaultValue: 1, name: 'count' });
|
||||
const count = ref(1);
|
||||
|
||||
const uuids = ref('');
|
||||
|
||||
|
Reference in New Issue
Block a user