Compare commits

...

25 Commits

Author SHA1 Message Date
renovate[bot]
d275661e5a chore(deps): update actions/setup-node action to v4 2023-10-23 17:10:39 +00:00
莫颓
02e68d3f56 feat(i18n): token generator (#688) 2023-10-23 09:17:08 +02:00
莫颓
00562ed5e8 feat(i18n): home page (#687)
(cherry picked from commit 9d39826078ceb929a5ca3b577f9f39449303c289)
2023-10-23 09:14:34 +02:00
renovate[bot]
4365226d01 chore(deps): update docker/setup-qemu-action action to v3 (#627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-22 10:30:26 +02:00
renovate[bot]
57ecda1623 chore(deps): update docker/setup-buildx-action action to v3 (#626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-22 10:30:16 +02:00
renovate[bot]
d8d7a3b9ab chore(deps): update docker/login-action action to v3 (#625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-22 10:30:04 +02:00
renovate[bot]
d36b18f193 chore(deps): update docker/build-push-action action to v5 (#624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-22 10:29:05 +02:00
renovate[bot]
eea9f91276 chore(deps): update dependency node to v18.18.2 (#674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-22 10:26:05 +02:00
莫颓
ebb4ec4165 feat(i18n): support for i18n in .ts files (#683)
(cherry picked from commit 732313bfc32a514ef064ca0f90304ff05e2e7ef3)
2023-10-22 10:23:00 +02:00
莫颓
84a4a646f6 feat(i18n): tool card (#682)
(cherry picked from commit 1d0a3904f72ab24364beda034d17cf6de9b5e959)
2023-10-22 08:21:10 +00:00
莫颓
a2b53c2e38 feat(i18n): about page (#680)
(cherry picked from commit 605b84c16e0eec1a16e6133203e7810a68447607)

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2023-10-22 08:15:39 +00:00
莫颓
35563b8457 feat(i18n): 404 page (#679) 2023-10-22 10:07:43 +02:00
Sellar
720201aa7b fix(deps): fix issue on slugify (#593) (#673) 2023-10-18 09:23:34 +02:00
Corentin THOMASSET
b2ad4f7a27 feat(new tool): text to ascii converter (#669) 2023-10-15 22:57:47 +00:00
Corentin Thomasset
b408df82c1 refactor(c-table): added description on c-table for accessibility 2023-10-15 23:41:55 +02:00
Corentin THOMASSET
88b881880c refactor(ci): reduced timeout in e2e (#666) 2023-10-14 22:48:18 +00:00
Corentin THOMASSET
ee4c853b9f refactor(ui): new c-table ui component (#665) 2023-10-14 22:45:14 +00:00
Corentin THOMASSET
cbf58fdd28 refactor(ui): removed n-page-header component in user-agent parser (#663) 2023-10-14 23:01:32 +02:00
Corentin THOMASSET
a757a5155a refactor(ui): removed n-p components in about page (#662) 2023-10-14 18:37:26 +02:00
Corentin THOMASSET
025f556023 refactor(ui): switched naive tooltip components to custom ones (#661) 2023-10-14 16:24:54 +00:00
renovate[bot]
2d2dffb14a chore(deps): update dependency node to v18.18.0 (#636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-04 11:46:33 +02:00
Corentin THOMASSET
5c4d775e2d feat(new tool): ULID generator (#623) 2023-09-11 22:57:42 +00:00
Corentin THOMASSET
557b30426f doc(readme): added contributors list (#622) 2023-09-11 20:59:07 +00:00
renovate[bot]
4972159aa7 chore(deps): update actions/checkout action to v4 (#613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 22:42:33 +02:00
renovate[bot]
e371ef702e fix(deps): update dependency monaco-editor to ^0.43.0 (#620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-11 22:40:42 +02:00
52 changed files with 1085 additions and 409 deletions

View File

@@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- run: corepack enable
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'pnpm'

View File

@@ -37,7 +37,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -12,7 +12,7 @@ jobs:
outputs:
should_run: ${{ steps.should_run.outputs.should_run }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- name: print latest_commit
run: echo ${{ github.sha }}
@@ -28,9 +28,9 @@ jobs:
if: ${{ needs.check_date.outputs.should_run != 'false' }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- run: corepack enable
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'pnpm'
@@ -54,29 +54,29 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile

View File

@@ -6,17 +6,17 @@ on:
- main
jobs:
test:
timeout-minutes: 60
timeout-minutes: 10
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1/3, 2/3, 3/3]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- run: corepack enable
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'pnpm'

View File

@@ -13,29 +13,29 @@ jobs:
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
@@ -55,11 +55,11 @@ jobs:
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
- run: corepack enable
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: 16
cache: 'pnpm'

2
.nvmrc
View File

@@ -1 +1 @@
18.17.1
18.18.2

View File

@@ -105,12 +105,20 @@ pnpm run script:create-new-tool my-tool-name
It will create a directory in `src/tools` with the correct files, and a the import in `src/tools/index.ts`. You will just need to add the imported tool in the proper category and develop the tool.
## Contributors
Big thanks to all the people who have already contributed!
[![contributors](https://contrib.rocks/image?repo=corentinth/it-tools)](https://github.com/corentinth/it-tools/graphs/contributors)
## Credits
Coded with ❤️ by [Corentin Thomasset](//corentin-thomasset.fr).
This project is continuously deployed using [vercel.com](https://vercel.com).
Contributor graph is generated using [contrib.rocks](https://contrib.rocks/preview?repo=corentinth/it-tools).
<a href="https://www.producthunt.com/posts/it-tools?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-it&#0045;tools" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=345793&theme=light" alt="IT&#0032;Tools - Collection&#0032;of&#0032;handy&#0032;online&#0032;tools&#0032;for&#0032;devs&#0044;&#0032;with&#0032;great&#0032;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&#0045;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&#0032;Tools - Collection&#0032;of&#0032;handy&#0032;online&#0032;tools&#0032;for&#0032;devs&#0044;&#0032;with&#0032;great&#0032;UX | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>

View File

@@ -0,0 +1,6 @@
---
to: src/ui/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.demo.vue
---
<template>
<<%= h.changeCase.param(name) %> />
</template>

View File

@@ -0,0 +1,13 @@
---
to: src/ui/<%= h.changeCase.param(name) %>/<%= h.changeCase.param(name) %>.vue
---
<script lang="ts" setup>
const props = withDefaults(defineProps<{ prop?: string }>(), { prop: '' });
const { prop } = toRefs(props);
</script>
<template>
<div>
{{ prop }}
</div>
</template>

9
components.d.ts vendored
View File

@@ -25,6 +25,8 @@ declare module '@vue/runtime-core' {
CaseConverter: typeof import('./src/tools/case-converter/case-converter.vue')['default']
CButton: typeof import('./src/ui/c-button/c-button.vue')['default']
'CButton.demo': typeof import('./src/ui/c-button/c-button.demo.vue')['default']
CButtonsSelect: typeof import('./src/ui/c-buttons-select/c-buttons-select.vue')['default']
'CButtonsSelect.demo': typeof import('./src/ui/c-buttons-select/c-buttons-select.demo.vue')['default']
CCard: typeof import('./src/ui/c-card/c-card.vue')['default']
'CCard.demo': typeof import('./src/ui/c-card/c-card.demo.vue')['default']
CDiffEditor: typeof import('./src/ui/c-diff-editor/c-diff-editor.vue')['default']
@@ -47,6 +49,8 @@ declare module '@vue/runtime-core' {
CrontabGenerator: typeof import('./src/tools/crontab-generator/crontab-generator.vue')['default']
CSelect: typeof import('./src/ui/c-select/c-select.vue')['default']
'CSelect.demo': typeof import('./src/ui/c-select/c-select.demo.vue')['default']
CTable: typeof import('./src/ui/c-table/c-table.vue')['default']
'CTable.demo': typeof import('./src/ui/c-table/c-table.demo.vue')['default']
CTextCopyable: typeof import('./src/ui/c-text-copyable/c-text-copyable.vue')['default']
'CTextCopyable.demo': typeof import('./src/ui/c-text-copyable/c-text-copyable.demo.vue')['default']
CTooltip: typeof import('./src/ui/c-tooltip/c-tooltip.vue')['default']
@@ -88,6 +92,8 @@ declare module '@vue/runtime-core' {
IconMdiDownload: typeof import('~icons/mdi/download')['default']
IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiFavoriteFilled: typeof import('~icons/mdi/favorite-filled')['default']
IconMdiHeart: typeof import('~icons/mdi/heart')['default']
IconMdiPause: typeof import('~icons/mdi/pause')['default']
IconMdiPlay: typeof import('~icons/mdi/play')['default']
IconMdiRecord: typeof import('~icons/mdi/record')['default']
@@ -176,6 +182,7 @@ declare module '@vue/runtime-core' {
TextareaCopyable: typeof import('./src/components/TextareaCopyable.vue')['default']
TextDiff: typeof import('./src/tools/text-diff/text-diff.vue')['default']
TextStatistics: typeof import('./src/tools/text-statistics/text-statistics.vue')['default']
TextToBinary: typeof import('./src/tools/text-to-binary/text-to-binary.vue')['default']
TextToNatoAlphabet: typeof import('./src/tools/text-to-nato-alphabet/text-to-nato-alphabet.vue')['default']
TokenDisplay: typeof import('./src/tools/otp-code-generator-and-validator/token-display.vue')['default']
'TokenGenerator.tool': typeof import('./src/tools/token-generator/token-generator.tool.vue')['default']
@@ -183,6 +190,8 @@ declare module '@vue/runtime-core' {
TomlToYaml: typeof import('./src/tools/toml-to-yaml/toml-to-yaml.vue')['default']
'Tool.layout': typeof import('./src/layouts/tool.layout.vue')['default']
ToolCard: typeof import('./src/components/ToolCard.vue')['default']
UlidGenerator: typeof import('./src/tools/ulid-generator/ulid-generator.vue')['default']
Unnamed: typeof import('./src/ui/unnamed/unnamed.vue')['default']
UrlEncoder: typeof import('./src/tools/url-encoder/url-encoder.vue')['default']
UrlParser: typeof import('./src/tools/url-parser/url-parser.vue')['default']
UserAgentParser: typeof import('./src/tools/user-agent-parser/user-agent-parser.vue')['default']

View File

@@ -1,4 +1,51 @@
home:
categories:
newestTools: Newest tools
favoriteTools: 'Your favorite tools'
allTools: 'All the tools'
subtitle: 'Handy tools for developers'
toggleMenu: 'Toggle menu'
home: Home
uiLib: 'UI Lib'
buyMeACoffee: 'Buy me a coffee'
follow:
title: 'You like it-tools?'
p1: 'Give us a star on'
githubRepository: 'IT-Tools GitHub repository'
p2: 'or follow us on'
twitterAccount: 'IT-Tools Twitter account'
thankYou: 'Thank you !'
nav:
github: 'GitHub repository'
githubRepository: 'IT-Tools GitHub repository'
twitter: 'Twitter account'
twitterAccount: 'IT Tools Twitter account'
about: 'About IT-Tools'
aboutLabel: 'About'
darkMode: 'Dark mode'
lightMode: 'Light mode'
mode: 'Toggle dark/light mode'
about:
h1: 'About IT-Tools'
h1p1: 'This wonderful website, made with ❤ by'
h1p2: ', aggregates useful tools for developer and people working in IT. If you find it useful, please feel free to share it to people you think may find it useful too and don''''t forget to bookmark it in your shortcut bar!'
h1p3: 'IT Tools is open-source (under the MIT license) and free, and will always be, but it costs me money to host and renew the domain name. If you want to support my work, and encourage me to add more tools, please consider supporting by'
h1p4: 'sponsoring me'
h2: Technologies
h2p1: '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 open-source libraries are used in some tools, you may find the complete list in the'
h2p2: 'file of the repository.'
h3: 'Found a bug? A tool is missing?'
h3p1: 'If you need a tool that is currently not present here, and you think can be useful, you are welcome to submit a feature request in the'
h3p2: 'issues section'
h3p3: 'in the GitHub repository.'
h3p4: 'And if you found a bug, or something doesn''''t work as expected, please file a bug report in the'
h3p5: 'issues section'
h3p6: 'in the GitHub repository.'
404:
notFound: '404 Not Found'
sorry: 'Sorry, this page does not seem to exist'
maybe: 'Maybe the cache is doing tricky things, try force-refreshing?'
backHome: 'Back home'
toolCard:
new: New

View File

@@ -30,13 +30,14 @@
"coverage": "vitest run --coverage",
"typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
"script:create-new-tool": "node scripts/create-tool.mjs",
"script:create:tool": "node scripts/create-tool.mjs",
"script:create:ui": "hygen generator ui-component",
"release": "node ./scripts/release.mjs"
},
"dependencies": {
"@it-tools/bip39": "^0.0.4",
"@it-tools/oggen": "^1.3.0",
"@sindresorhus/slugify": "^2.2.0",
"@sindresorhus/slugify": "^2.2.1",
"@tiptap/pm": "^2.1.6",
"@tiptap/starter-kit": "^2.1.6",
"@tiptap/vue-3": "^2.0.3",
@@ -66,7 +67,7 @@
"lodash": "^4.17.21",
"mathjs": "^11.9.1",
"mime-types": "^2.1.35",
"monaco-editor": "^0.41.0",
"monaco-editor": "^0.43.0",
"naive-ui": "^2.34.3",
"netmask": "^2.0.2",
"node-forge": "^1.3.1",
@@ -77,6 +78,7 @@
"randombytes": "^2.1.0",
"sql-formatter": "^13.0.0",
"ua-parser-js": "^1.0.35",
"ulid": "^2.3.0",
"unicode-emoji-json": "^0.4.0",
"unplugin-auto-import": "^0.16.4",
"uuid": "^9.0.0",
@@ -116,6 +118,7 @@
"c8": "^8.0.0",
"consola": "^3.0.2",
"eslint": "^8.47.0",
"hygen": "^6.2.11",
"jsdom": "^22.0.0",
"less": "^4.1.3",
"prettier": "^3.0.0",

369
pnpm-lock.yaml generated
View File

@@ -12,8 +12,8 @@ dependencies:
specifier: ^1.3.0
version: 1.3.0
'@sindresorhus/slugify':
specifier: ^2.2.0
version: 2.2.0
specifier: ^2.2.1
version: 2.2.1
'@tiptap/pm':
specifier: ^2.1.6
version: 2.1.6
@@ -102,8 +102,8 @@ dependencies:
specifier: ^2.1.35
version: 2.1.35
monaco-editor:
specifier: ^0.41.0
version: 0.41.0
specifier: ^0.43.0
version: 0.43.0
naive-ui:
specifier: ^2.34.3
version: 2.34.3(vue@3.3.4)
@@ -134,6 +134,9 @@ dependencies:
ua-parser-js:
specifier: ^1.0.35
version: 1.0.35
ulid:
specifier: ^2.3.0
version: 2.3.0
unicode-emoji-json:
specifier: ^0.4.0
version: 0.4.0
@@ -247,6 +250,9 @@ devDependencies:
eslint:
specifier: ^8.47.0
version: 8.47.0
hygen:
specifier: ^6.2.11
version: 6.2.11
jsdom:
specifier: ^22.0.0
version: 22.0.0
@@ -2517,8 +2523,8 @@ packages:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
/@sindresorhus/slugify@2.2.0:
resolution: {integrity: sha512-9Vybc/qX8Kj6pxJaapjkFbiUJPk7MAkCh/GFCxIBnnsuYCFPIXKvnLidG8xlepht3i24L5XemUmGtrJ3UWrl6w==}
/@sindresorhus/slugify@2.2.1:
resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==}
engines: {node: '>=12'}
dependencies:
'@sindresorhus/transliterate': 1.6.0
@@ -2915,6 +2921,10 @@ packages:
'@types/node': 18.15.11
dev: true
/@types/node@17.0.45:
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
dev: true
/@types/node@18.15.11:
resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
dev: true
@@ -3269,7 +3279,7 @@ packages:
dependencies:
'@unhead/dom': 0.5.1
'@unhead/schema': 0.5.1
'@vueuse/shared': 10.4.1(vue@3.3.4)
'@vueuse/shared': 10.5.0(vue@3.3.4)
unhead: 0.5.1
vue: 3.3.4
transitivePeerDependencies:
@@ -3851,10 +3861,10 @@ packages:
- vue
dev: false
/@vueuse/shared@10.4.1(vue@3.3.4):
resolution: {integrity: sha512-vz5hbAM4qA0lDKmcr2y3pPdU+2EVw/yzfRsBdu+6+USGa4PxqSQRYIUC9/NcT06y+ZgaTsyURw2I9qOFaaXHAg==}
/@vueuse/shared@10.5.0(vue@3.3.4):
resolution: {integrity: sha512-18iyxbbHYLst9MqU1X1QNdMHIjks6wC7XTVf0KNOv5es/Ms6gjVFCAAWTVP2JStuGqydg3DT+ExpFORUEi9yhg==}
dependencies:
vue-demi: 0.14.5(vue@3.3.4)
vue-demi: 0.14.6(vue@3.3.4)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
@@ -3931,6 +3941,11 @@ packages:
uri-js: 4.4.1
dev: true
/ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
dev: true
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -4066,6 +4081,10 @@ packages:
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
/base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true
/bcryptjs@2.4.3:
resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==}
dev: false
@@ -4074,6 +4093,14 @@ packages:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
dev: true
/boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
dev: true
@@ -4119,6 +4146,13 @@ packages:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true
/buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
dev: true
/builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'}
@@ -4165,6 +4199,13 @@ packages:
engines: {node: '>=6'}
dev: true
/camel-case@3.0.0:
resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==}
dependencies:
no-case: 2.3.2
upper-case: 1.1.3
dev: true
/camel-case@4.1.2:
resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
dependencies:
@@ -4236,6 +4277,29 @@ packages:
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
dev: true
/change-case@3.1.0:
resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}
dependencies:
camel-case: 3.0.0
constant-case: 2.0.0
dot-case: 2.1.1
header-case: 1.0.1
is-lower-case: 1.1.3
is-upper-case: 1.1.2
lower-case: 1.1.4
lower-case-first: 1.0.2
no-case: 2.3.2
param-case: 2.1.1
pascal-case: 2.0.1
path-case: 2.1.1
sentence-case: 2.1.1
snake-case: 2.1.0
swap-case: 1.1.2
title-case: 2.1.1
upper-case: 1.1.3
upper-case-first: 1.1.2
dev: true
/change-case@4.1.2:
resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
dependencies:
@@ -4299,6 +4363,18 @@ packages:
escape-string-regexp: 1.0.5
dev: true
/cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
dependencies:
restore-cursor: 3.1.0
dev: true
/cli-spinners@2.9.1:
resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==}
engines: {node: '>=6'}
dev: true
/cliui@6.0.0:
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
dependencies:
@@ -4315,6 +4391,11 @@ packages:
wrap-ansi: 7.0.0
dev: true
/clone@1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
dev: true
/color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
@@ -4399,6 +4480,13 @@ packages:
engines: {node: ^14.18.0 || >=16.10.0}
dev: true
/constant-case@2.0.0:
resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==}
dependencies:
snake-case: 2.1.0
upper-case: 1.1.3
dev: true
/constant-case@3.0.4:
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
dependencies:
@@ -4622,6 +4710,12 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
/defaults@1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
dependencies:
clone: 1.0.4
dev: true
/define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
@@ -4639,6 +4733,12 @@ packages:
resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==}
dev: true
/degit@2.8.4:
resolution: {integrity: sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng==}
engines: {node: '>=8.0.0'}
hasBin: true
dev: true
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -4721,6 +4821,12 @@ packages:
domhandler: 5.0.3
dev: true
/dot-case@2.1.1:
resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==}
dependencies:
no-case: 2.3.2
dev: true
/dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
dependencies:
@@ -4771,6 +4877,14 @@ packages:
resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
dev: false
/enquirer@2.4.1:
resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
engines: {node: '>=8.6'}
dependencies:
ansi-colors: 4.1.3
strip-ansi: 6.0.1
dev: true
/entities@3.0.1:
resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==}
engines: {node: '>=0.12'}
@@ -5480,6 +5594,21 @@ packages:
resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
dev: true
/front-matter@4.0.2:
resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==}
dependencies:
js-yaml: 3.14.1
dev: true
/fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: true
/fs-extra@11.1.1:
resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
engines: {node: '>=14.14'}
@@ -5750,6 +5879,13 @@ packages:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
dev: false
/header-case@1.0.1:
resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==}
dependencies:
no-case: 2.3.2
upper-case: 1.1.3
dev: true
/header-case@2.0.4:
resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
dependencies:
@@ -5837,6 +5973,28 @@ packages:
ms: 2.1.3
dev: false
/hygen@6.2.11:
resolution: {integrity: sha512-t6/zLI2XozP5gvV74nnl8LZSbwpVNFUkUs/O9DwuOdiiBbws5k4AQNVwKZ9FGzcKjdJ5EBBYkVzlcUHkLyY0FQ==}
hasBin: true
dependencies:
'@types/node': 17.0.45
chalk: 4.1.2
change-case: 3.1.0
debug: 4.3.4
degit: 2.8.4
ejs: 3.1.9
enquirer: 2.4.1
execa: 5.1.1
front-matter: 4.0.2
fs-extra: 10.1.0
ignore-walk: 4.0.1
inflection: 1.13.4
ora: 5.4.1
yargs-parser: 21.1.1
transitivePeerDependencies:
- supports-color
dev: true
/iarna-toml-esm@3.0.5:
resolution: {integrity: sha512-CgeDbPohnFG827UoRaCqKxJ8idiIDZDWlcHf5hUReQnZ8jHnNnhN4QJFiY12fKvr0LvuDuKAimqQfrmQnacbtw==}
dependencies:
@@ -5858,6 +6016,17 @@ packages:
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
dev: true
/ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
dev: true
/ignore-walk@4.0.1:
resolution: {integrity: sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==}
engines: {node: '>=10'}
dependencies:
minimatch: 3.1.2
dev: true
/ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
engines: {node: '>= 4'}
@@ -5888,6 +6057,11 @@ packages:
engines: {node: '>=8'}
dev: true
/inflection@1.13.4:
resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==}
engines: {'0': node >= 0.4.0}
dev: true
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
dependencies:
@@ -6033,6 +6207,17 @@ packages:
resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}
dev: true
/is-interactive@1.0.0:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
dev: true
/is-lower-case@1.1.3:
resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==}
dependencies:
lower-case: 1.1.4
dev: true
/is-module@1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
dev: true
@@ -6124,6 +6309,17 @@ packages:
which-typed-array: 1.1.11
dev: true
/is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
dev: true
/is-upper-case@1.1.2:
resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==}
dependencies:
upper-case: 1.1.3
dev: true
/is-weakref@1.0.2:
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
dependencies:
@@ -6455,12 +6651,30 @@ packages:
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
/log-symbols@4.1.0:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
dependencies:
chalk: 4.1.2
is-unicode-supported: 0.1.0
dev: true
/loupe@2.3.6:
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
dependencies:
get-func-name: 2.0.0
dev: true
/lower-case-first@1.0.2:
resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}
dependencies:
lower-case: 1.1.4
dev: true
/lower-case@1.1.4:
resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}
dev: true
/lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies:
@@ -6659,8 +6873,8 @@ packages:
pkg-types: 1.0.3
ufo: 1.1.2
/monaco-editor@0.41.0:
resolution: {integrity: sha512-1o4olnZJsiLmv5pwLEAmzHTE/5geLKQ07BrGxlF4Ri/AXAc2yyDGZwHjiTqD8D/ROKUZmwMA28A+yEowLNOEcA==}
/monaco-editor@0.43.0:
resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==}
dev: false
/moo@0.5.2:
@@ -6744,6 +6958,12 @@ packages:
engines: {node: '>= 0.4.0'}
dev: false
/no-case@2.3.2:
resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
dependencies:
lower-case: 1.1.4
dev: true
/no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
dependencies:
@@ -6916,6 +7136,21 @@ packages:
type-check: 0.4.0
dev: true
/ora@5.4.1:
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
engines: {node: '>=10'}
dependencies:
bl: 4.1.0
chalk: 4.1.2
cli-cursor: 3.1.0
cli-spinners: 2.9.1
is-interactive: 1.0.0
is-unicode-supported: 0.1.0
log-symbols: 4.1.0
strip-ansi: 6.0.1
wcwidth: 1.0.1
dev: true
/orderedmap@2.1.1:
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
dev: false
@@ -6973,6 +7208,12 @@ packages:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
/param-case@2.1.1:
resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
dependencies:
no-case: 2.3.2
dev: true
/param-case@3.0.4:
resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
dependencies:
@@ -7019,6 +7260,13 @@ packages:
entities: 4.5.0
dev: true
/pascal-case@2.0.1:
resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==}
dependencies:
camel-case: 3.0.0
upper-case-first: 1.1.2
dev: true
/pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
dependencies:
@@ -7026,6 +7274,12 @@ packages:
tslib: 2.5.0
dev: false
/path-case@2.1.1:
resolution: {integrity: sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==}
dependencies:
no-case: 2.3.2
dev: true
/path-case@3.0.4:
resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
dependencies:
@@ -7432,6 +7686,15 @@ packages:
type-fest: 0.6.0
dev: true
/readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: true
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@@ -7545,6 +7808,14 @@ packages:
supports-preserve-symlinks-flag: 1.0.0
dev: true
/restore-cursor@3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
dev: true
/ret@0.1.15:
resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
engines: {node: '>=0.12'}
@@ -7681,6 +7952,13 @@ packages:
lru-cache: 6.0.0
dev: true
/sentence-case@2.1.1:
resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==}
dependencies:
no-case: 2.3.2
upper-case-first: 1.1.2
dev: true
/sentence-case@3.0.4:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
dependencies:
@@ -7758,6 +8036,12 @@ packages:
engines: {node: '>=12'}
dev: true
/snake-case@2.1.0:
resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==}
dependencies:
no-case: 2.3.2
dev: true
/snake-case@3.0.4:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
dependencies:
@@ -7901,6 +8185,12 @@ packages:
es-abstract: 1.22.1
dev: true
/string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
dependencies:
safe-buffer: 5.2.1
dev: true
/stringify-object@3.3.0:
resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
engines: {node: '>=4'}
@@ -7982,6 +8272,13 @@ packages:
picocolors: 1.0.0
dev: true
/swap-case@1.1.2:
resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==}
dependencies:
lower-case: 1.1.4
upper-case: 1.1.3
dev: true
/symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: true
@@ -8072,6 +8369,13 @@ packages:
'@popperjs/core': 2.11.8
dev: false
/title-case@2.1.1:
resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
dependencies:
no-case: 2.3.2
upper-case: 1.1.3
dev: true
/to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
@@ -8246,6 +8550,11 @@ packages:
/ufo@1.1.2:
resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
/ulid@2.3.0:
resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==}
hasBin: true
dev: false
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@@ -8519,12 +8828,22 @@ packages:
escalade: 3.1.1
picocolors: 1.0.0
/upper-case-first@1.1.2:
resolution: {integrity: sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==}
dependencies:
upper-case: 1.1.3
dev: true
/upper-case-first@2.0.2:
resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==}
dependencies:
tslib: 2.5.0
dev: false
/upper-case@1.1.3:
resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==}
dev: true
/upper-case@2.0.2:
resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==}
dependencies:
@@ -8782,6 +9101,21 @@ packages:
vue: 3.3.4
dev: false
/vue-demi@0.14.6(vue@3.3.4):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: ^3.0.0-0 || ^2.6.0
peerDependenciesMeta:
'@vue/composition-api':
optional: true
dependencies:
vue: 3.3.4
dev: false
/vue-eslint-parser@9.3.1(eslint@8.47.0):
resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
engines: {node: ^14.17.0 || >=16.0.0}
@@ -8875,6 +9209,12 @@ packages:
xml-name-validator: 4.0.0
dev: true
/wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies:
defaults: 1.0.4
dev: true
/web-streams-polyfill@3.2.1:
resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
engines: {node: '>= 8'}
@@ -9244,6 +9584,11 @@ packages:
engines: {node: '>=10'}
dev: true
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
/yargs@15.4.1:
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
engines: {node: '>=8'}

View File

@@ -1,6 +1,4 @@
<script setup lang="ts">
import { FavoriteFilled } from '@vicons/material';
import { useToolStore } from '@/tools/tools.store';
import type { Tool } from '@/tools/tools.types';
@@ -26,18 +24,15 @@ function toggleFavorite(event: MouseEvent) {
</script>
<template>
<n-tooltip trigger="hover">
<template #trigger>
<c-button
variant="text"
circle
:type="buttonType"
:style="{ opacity: isFavorite ? 1 : 0.2 }"
@click="toggleFavorite"
>
<n-icon :component="FavoriteFilled" />
</c-button>
</template>
{{ isFavorite ? 'Remove from favorites' : 'Add to favorites' }}
</n-tooltip>
<c-tooltip :tooltip="isFavorite ? 'Remove from favorites' : 'Add to favorites' ">
<c-button
variant="text"
circle
:type="buttonType"
:style="{ opacity: isFavorite ? 1 : 0.2 }"
@click="toggleFavorite"
>
<icon-mdi-heart />
</c-button>
</c-tooltip>
</template>

View File

@@ -13,14 +13,11 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : 'Copy to cli
<template>
<c-input-text v-model:value="value">
<template #suffix>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" size="small" @click="copy()">
<icon-mdi-content-copy />
</c-button>
</template>
{{ tooltipText }}
</n-tooltip>
<c-tooltip :tooltip="tooltipText">
<c-button circle variant="text" size="small" @click="copy()">
<icon-mdi-content-copy />
</c-button>
</c-tooltip>
</template>
</c-input-text>
</template>

View File

@@ -7,56 +7,43 @@ const { isDarkTheme } = toRefs(styleStore);
</script>
<template>
<n-tooltip trigger="hover">
<template #trigger>
<c-button
circle
variant="text"
href="https://github.com/CorentinTh/it-tools"
target="_blank"
rel="noopener noreferrer"
aria-label="IT-Tools' GitHub repository"
>
<n-icon size="25" :component="BrandGithub" />
</c-button>
</template>
Github repository
</n-tooltip>
<c-tooltip :tooltip="$t('home.nav.github')" position="bottom">
<c-button
circle
variant="text"
href="https://github.com/CorentinTh/it-tools"
target="_blank"
rel="noopener noreferrer"
:aria-label="$t('home.nav.githubRepository')"
>
<n-icon size="25" :component="BrandGithub" />
</c-button>
</c-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<c-button
circle
variant="text"
href="https://twitter.com/ittoolsdottech"
rel="noopener"
target="_blank"
aria-label="IT Tools' Twitter account"
>
<n-icon size="25" :component="BrandTwitter" />
</c-button>
</template>
IT Tools' Twitter account
</n-tooltip>
<c-tooltip :tooltip="$t('home.nav.twitter')" position="bottom">
<c-button
circle
variant="text"
href="https://twitter.com/ittoolsdottech"
rel="noopener"
target="_blank"
:aria-label="$t('home.nav.twitterAccount')"
>
<n-icon size="25" :component="BrandTwitter" />
</c-button>
</c-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" to="/about" aria-label="About">
<n-icon size="25" :component="InfoCircle" />
</c-button>
</template>
About
</n-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" aria-label="Toggle dark/light mode" @click="() => styleStore.toggleDark()">
<n-icon v-if="isDarkTheme" size="25" :component="Sun" />
<n-icon v-else size="25" :component="Moon" />
</c-button>
</template>
<span v-if="isDarkTheme">Light mode</span>
<span v-else>Dark mode</span>
</n-tooltip>
<c-tooltip :tooltip="$t('home.nav.about')" position="bottom">
<c-button circle variant="text" to="/about" :aria-label="$t('home.nav.aboutLabel')">
<n-icon size="25" :component="InfoCircle" />
</c-button>
</c-tooltip>
<c-tooltip :tooltip="isDarkTheme ? $t('home.nav.lightMode') : $t('home.nav.darkMode')" position="bottom">
<c-button circle variant="text" :aria-label="$t('home.nav.mode')" @click="() => styleStore.toggleDark()">
<n-icon v-if="isDarkTheme" size="25" :component="Sun" />
<n-icon v-else size="25" :component="Moon" />
</c-button>
</c-tooltip>
</template>
<style lang="less" scoped>

View File

@@ -11,17 +11,7 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : initialText)
</script>
<template>
<n-tooltip trigger="hover">
<template #trigger>
<span class="value" @click="copy()">{{ value }}</span>
</template>
{{ tooltipText }}
</n-tooltip>
<c-tooltip :tooltip="tooltipText">
<span cursor-pointer font-mono @click="copy()">{{ value }}</span>
</c-tooltip>
</template>
<style scoped lang="less">
.value {
cursor: pointer;
font-family: monospace;
}
</style>

View File

@@ -40,7 +40,7 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
<template>
<div style="overflow-x: hidden; width: 100%">
<c-card class="result-card">
<c-card relative>
<n-scrollbar
x-scrollable
trigger="none"
@@ -50,16 +50,13 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
<n-code :code="value" :language="language" :trim="false" data-test-id="area-content" />
</n-config-provider>
</n-scrollbar>
<n-tooltip v-if="value" trigger="hover">
<template #trigger>
<div class="copy-button" :class="[copyPlacement]">
<c-button circle important:h-10 important:w-10 @click="copy()">
<n-icon size="22" :component="Copy" />
</c-button>
</div>
</template>
<span>{{ tooltipText }}</span>
</n-tooltip>
<div absolute right-10px top-10px>
<c-tooltip v-if="value" :tooltip="tooltipText" position="left">
<c-button circle important:h-10 important:w-10 @click="copy()">
<n-icon size="22" :component="Copy" />
</c-button>
</c-tooltip>
</div>
</c-card>
<div v-if="copyPlacement === 'outside'" mt-4 flex justify-center>
<c-button @click="copy()">
@@ -74,25 +71,4 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
padding-bottom: 10px;
margin-bottom: -10px;
}
.result-card {
position: relative;
.copy-button {
position: absolute;
opacity: 1;
&.top-right {
top: 10px;
right: 10px;
}
&.bottom-right {
bottom: 10px;
right: 10px;
}
&.outside,
&.none {
display: none;
}
}
}
</style>

View File

@@ -26,7 +26,7 @@ const appTheme = useAppTheme();
:bordered="false"
:color="{ color: theme.primaryColor, textColor: theme.tagColor }"
>
New
{{ $t('toolCard.new') }}
</n-tag>
<FavoriteButton :tool="tool" />

View File

@@ -41,7 +41,7 @@ const tools = computed<ToolCategory[]>(() => [
</div>
<div class="divider" />
<div class="subtitle">
Handy tools for developers
{{ $t('home.subtitle') }}
</div>
</div>
</RouterLink>
@@ -88,24 +88,23 @@ const tools = computed<ToolCategory[]>(() => [
<c-button
circle
variant="text"
aria-label="Toggle menu"
:aria-label="$t('home.toggleMenu')"
@click="styleStore.isMenuCollapsed = !styleStore.isMenuCollapsed"
>
<NIcon size="25" :component="Menu2" />
</c-button>
<n-tooltip trigger="hover">
<template #trigger>
<c-button to="/" circle variant="text" aria-label="Home">
<NIcon size="25" :component="Home2" />
</c-button>
</template>
Home
</n-tooltip>
<c-tooltip tooltip="Home" position="bottom">
<c-button to="/" circle variant="text" :aria-label="$t('home.home')">
<NIcon size="25" :component="Home2" />
</c-button>
</c-tooltip>
<c-button v-if="config.app.env === 'development'" to="/c-lib" circle variant="text" aria-label="UI Lib">
<icon-mdi:brush-variant text-20px />
</c-button>
<c-tooltip tooltip="UI Lib" position="bottom">
<c-button v-if="config.app.env === 'development'" to="/c-lib" circle variant="text" :aria-label="$t('home.uiLib')">
<icon-mdi:brush-variant text-20px />
</c-button>
</c-tooltip>
<command-palette />
@@ -113,23 +112,20 @@ const tools = computed<ToolCategory[]>(() => [
<NavbarButtons v-if="!styleStore.isSmallScreen" />
</div>
<n-tooltip trigger="hover">
<template #trigger>
<c-button
round
href="https://www.buymeacoffee.com/cthmsst"
rel="noopener"
target="_blank"
class="support-button"
:bordered="false"
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
>
Buy me a coffee
<NIcon v-if="!styleStore.isSmallScreen" :component="Heart" ml-2 />
</c-button>
</template>
Support IT Tools development !
</n-tooltip>
<c-tooltip position="bottom" tooltip="Support IT Tools development">
<c-button
round
href="https://www.buymeacoffee.com/cthmsst"
rel="noopener"
target="_blank"
class="support-button"
:bordered="false"
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
>
{{ $t('home.buyMeACoffee') }}
<NIcon v-if="!styleStore.isSmallScreen" :component="Heart" ml-2 />
</c-button>
</c-tooltip>
</div>
<slot />
</template>

View File

@@ -11,17 +11,17 @@ useHead({ title: 'Page not found - IT Tools' });
</span>
<h1 m-0 mt-3>
404 Not Found
{{ $t('404.notFound') }}
</h1>
<div mt-4 op-60>
Sorry, this page does not seem to exist
{{ $t('404.sorry') }}
</div>
<div mb-8 op-60>
Maybe the cache is doing tricky things, try force-refreshing?
{{ $t('404.maybe') }}
</div>
<c-button to="/">
Back home
{{ $t('404.backHome') }}
</c-button>
</div>
</template>

View File

@@ -7,79 +7,57 @@ const { tracker } = useTracker();
</script>
<template>
<div class="about-page">
<n-h1>About</n-h1>
<n-p>
This wonderful website, made with by
<div mx-auto mt-50px max-w-600px>
<h1>{{ $t('about.h1') }}</h1>
<p text-justify>
{{ $t('about.h1p1') }}
<c-link href="https://github.com/CorentinTh" target="_blank" rel="noopener">
Corentin Thomasset
</c-link>,
aggregates useful tools for developer and people working in IT. If you find it useful, please feel free to share
it to people you think may find it useful too and don't forget to bookmark it in your shortcut bar!
</n-p>
<n-p>
IT Tools is open-source (under the MIT license) and free, and will always be, but it costs me money to host and
renew the domain name. If you want to support my work, and encourage me to add more tools, please consider
supporting by
</c-link>{{ $t('about.h1p2') }}
</p>
<p text-justify>
{{ $t('about.h1p3') }}
<c-link
href="https://www.buymeacoffee.com/cthmsst"
rel="noopener"
target="_blank"
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
>
sponsoring me
{{ $t('about.h1p4') }}
</c-link>.
</n-p>
</p>
<n-h2>Technologies</n-h2>
<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 open-source libraries are used in some tools, you may find the complete list in the
<h2>{{ $t('about.h2') }}</h2>
<p text-justify>
{{ $t('about.h2p1') }}
<c-link href="https://github.com/CorentinTh/it-tools/blob/main/package.json" rel="noopener" target="_blank">
package.json
</c-link>
file of the repository.
</n-p>
{{ $t('about.h2p2') }}
</p>
<n-h2>Found a bug? A tool is missing?</n-h2>
<n-p>
If you need a tool that is currently not present here, and you think can be useful, you are welcome to submit a
feature request in the
<h2>{{ $t('about.h3') }}</h2>
<p text-justify>
{{ $t('about.h3p1') }}
<c-link
href="https://github.com/CorentinTh/it-tools/issues/new/choose"
rel="noopener"
target="_blank"
>
issues section
{{ $t('about.h3p2') }}
</c-link>
in the GitHub repository.
</n-p>
<n-p>
And if you found a bug, or something doesn't work as expected, please file a bug report in the
{{ $t('about.h3p3') }}
</p>
<p text-justify>
{{ $t('about.h3p4') }}
<c-link
href="https://github.com/CorentinTh/it-tools/issues/new/choose"
rel="noopener"
target="_blank"
>
issues section
{{ $t('about.h3p5') }}
</c-link>
in the GitHub repository.
</n-p>
{{ $t('about.h3p6') }}
</p>
</div>
</template>
<style scoped lang="less">
.about-page {
max-width: 600px;
margin: 50px auto;
box-sizing: border-box;
.n-h2 {
margin-bottom: 0px;
}
.n-p {
text-align: justify;
}
}
</style>

View File

@@ -17,21 +17,21 @@ const { t } = useI18n();
<div class="grid-wrapper">
<n-grid v-if="config.showBanner" x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi>
<ColoredCard title="You like it-tools?" :icon="Heart">
Give us a star on
<ColoredCard :title="$t('home.follow.title')" :icon="Heart">
{{ $t('home.follow.p1') }}
<a
href="https://github.com/CorentinTh/it-tools"
rel="noopener"
target="_blank"
aria-label="IT-Tools' GitHub repository"
:aria-label="$t('home.follow.githubRepository')"
>GitHub</a>
or follow us on
{{ $t('home.follow.p2') }}
<a
href="https://twitter.com/ittoolsdottech"
rel="noopener"
target="_blank"
aria-label="IT-Tools' Twitter account"
>Twitter</a>! Thank you
:aria-label="$t('home.follow.twitterAccount')"
>Twitter</a>{{ $t('home.follow.thankYou') }}
<n-icon :component="Heart" />
</ColoredCard>
</n-gi>
@@ -39,7 +39,7 @@ const { t } = useI18n();
<transition name="height">
<div v-if="toolStore.favoriteTools.length > 0">
<n-h3>Your favorite tools</n-h3>
<n-h3>{{ $t('home.categories.favoriteTools') }}</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.favoriteTools" :key="tool.name">
<ToolCard :tool="tool" />
@@ -57,7 +57,7 @@ const { t } = useI18n();
</n-grid>
</div>
<n-h3>All the tools</n-h3>
<n-h3>{{ $t('home.categories.allTools') }}</n-h3>
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi v-for="tool in toolStore.tools" :key="tool.name">
<transition>

View File

@@ -29,3 +29,9 @@ export const i18nPlugin: Plugin = {
app.use(i18n);
},
};
export const translate = function (localeKey: string) {
// @ts-expect-error global
const hasKey = i18n.global.te(localeKey, i18n.global.locale);
return hasKey ? i18n.global.t(localeKey) : localeKey;
};

View File

@@ -51,11 +51,11 @@ const results = computed(() => {
const { copy } = useCopy({ createToast: false });
const header = {
position: 'Position',
title: 'Suite',
size: 'Samples',
mean: 'Mean',
variance: 'Variance',
position: 'Position',
};
function copyAsMarkdown() {
@@ -131,26 +131,8 @@ function copyAsBulletList() {
</c-button>
</div>
<n-table>
<thead>
<tr>
<th>{{ header.position }}</th>
<th>{{ header.title }}</th>
<th>{{ header.size }}</th>
<th>{{ header.mean }}</th>
<th>{{ header.variance }}</th>
</tr>
</thead>
<tbody>
<tr v-for="{ title, size, mean, variance, position } of results" :key="title">
<td>{{ position }}</td>
<td>{{ title }}</td>
<td>{{ size }}</td>
<td>{{ mean }}</td>
<td>{{ variance }}</td>
</tr>
</tbody>
</n-table>
<c-table :data="results" :headers="header" />
<div mt-5 flex justify-center gap-3>
<c-button @click="copyAsMarkdown()">
Copy as markdown table

View File

@@ -39,14 +39,11 @@ function onInputEnter(index: number) {
autofocus
@keydown.enter="onInputEnter(index)"
/>
<n-tooltip>
<template #trigger>
<c-button circle variant="text" @click="values.splice(index, 1)">
<n-icon :component="Trash" depth="3" size="18" />
</c-button>
</template>
Delete value
</n-tooltip>
<c-tooltip tooltip="Delete this value">
<c-button circle variant="text" @click="values.splice(index, 1)">
<n-icon :component="Trash" depth="3" size="18" />
</c-button>
</c-tooltip>
</div>
<c-button @click="addValue">

View File

@@ -167,34 +167,8 @@ const cronValidationRules = [
</div>
</c-card>
</div>
<n-table v-else size="small">
<thead>
<tr>
<th class="text-left" scope="col">
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>
</thead>
<tbody>
<tr v-for="{ symbol, meaning, example, equivalent } in helpers" :key="symbol">
<td>{{ symbol }}</td>
<td>{{ meaning }}</td>
<td>
<code>{{ example }}</code>
</td>
<td>{{ equivalent }}</td>
</tr>
</tbody>
</n-table>
<c-table v-else :data="helpers" />
</c-card>
</template>

View File

@@ -6,13 +6,9 @@ const { icon, title, action, isActive } = toRefs(props);
</script>
<template>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" :type="isActive?.() ? 'primary' : 'default'" @click="action">
<n-icon :component="icon" />
</c-button>
</template>
{{ title }}
</n-tooltip>
<c-tooltip :tooltip="title">
<c-button circle variant="text" :type="isActive?.() ? 'primary' : 'default'" @click="action">
<n-icon :component="icon" />
</c-button>
</c-tooltip>
</template>

View File

@@ -1,6 +1,8 @@
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 textToBinary } from './text-to-binary';
import { tool as ulidGenerator } from './ulid-generator';
import { tool as ibanValidatorAndParser } from './iban-validator-and-parser';
import { tool as stringObfuscator } from './string-obfuscator';
import { tool as textDiff } from './text-diff';
@@ -74,7 +76,7 @@ import { tool as xmlFormatter } from './xml-formatter';
export const toolsByCategory: ToolCategory[] = [
{
name: 'Crypto',
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser],
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser],
},
{
name: 'Converter',
@@ -87,6 +89,7 @@ export const toolsByCategory: ToolCategory[] = [
colorConverter,
caseConverter,
textToNatoAlphabet,
textToBinary,
yamlToJson,
yamlToToml,
jsonToYaml,

View File

@@ -61,19 +61,16 @@ const secretValidationRules = [
:validation-rules="secretValidationRules"
>
<template #suffix>
<n-tooltip trigger="hover">
<template #trigger>
<c-button circle variant="text" size="small" @click="refreshSecret">
<icon-mdi-refresh />
</c-button>
</template>
Generate secret token
</n-tooltip>
<c-tooltip tooltip="Generate a new random secret">
<c-button circle variant="text" size="small" @click="refreshSecret">
<icon-mdi-refresh />
</c-button>
</c-tooltip>
</template>
</c-input-text>
<div>
<TokenDisplay :tokens="tokens" style="margin-top: 2px" />
<TokenDisplay :tokens="tokens" />
<n-progress :percentage="(100 * interval) / 30" :color="theme.primaryColor" :show-indicator="false" />
<div style="text-align: center">

View File

@@ -11,7 +11,7 @@ const { tokens } = toRefs(props);
<template>
<div>
<div class="labels" w-full flex items-center>
<div mb-5px w-full flex items-center>
<div flex-1 text-left>
Previous
</div>
@@ -22,60 +22,24 @@ const { tokens } = toRefs(props);
Next
</div>
</div>
<n-input-group>
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<c-button important:h-12 data-test-id="previous-otp" @click.prevent="copyPrevious(tokens.previous)">
{{ tokens.previous }}
</c-button>
</template>
<div>{{ previousCopied ? 'Copied !' : 'Copy previous OTP' }}</div>
</n-tooltip>
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<c-button
data-test-id="current-otp"
class="current-otp"
important:h-12
@click.prevent="copyCurrent(tokens.current)"
>
{{ tokens.current }}
</c-button>
</template>
<div>{{ currentCopied ? 'Copied !' : 'Copy current OTP' }}</div>
</n-tooltip>
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<c-button important:h-12 data-test-id="next-otp" @click.prevent="copyNext(tokens.next)">
{{
tokens.next
}}
</c-button>
</template>
<div>{{ nextCopied ? 'Copied !' : 'Copy next OTP' }}</div>
</n-tooltip>
</n-input-group>
<div flex items-center>
<c-tooltip :tooltip="previousCopied ? 'Copied !' : 'Copy previous OTP'" position="bottom" flex-1>
<c-button data-test-id="previous-otp" w-full important:h-12 important:rounded-r-none important:font-mono @click.prevent="copyPrevious(tokens.previous)">
{{ tokens.previous }}
</c-button>
</c-tooltip>
<c-tooltip :tooltip="currentCopied ? 'Copied !' : 'Copy current OTP'" position="bottom" flex-1 flex-basis-5xl>
<c-button
data-test-id="current-otp" w-full important:border-x="1px solid gray op-40" important:h-12 important:rounded-0 important:text-22px @click.prevent="copyCurrent(tokens.current)"
>
{{ tokens.current }}
</c-button>
</c-tooltip>
<c-tooltip :tooltip="nextCopied ? 'Copied !' : 'Copy next OTP'" position="bottom" flex-1>
<c-button data-test-id="next-otp" w-full important:h-12 important:rounded-l-none @click.prevent="copyNext(tokens.next)">
{{ tokens.next }}
</c-button>
</c-tooltip>
</div>
</div>
</template>
<style scoped lang="less">
.current-otp {
font-size: 22px;
flex: 1 0 35% !important;
}
.n-button {
height: 45px;
}
.labels {
div {
padding: 0 2px 6px 2px;
line-height: 1.25;
}
}
.n-input-group > * {
flex: 1 0 0;
}
</style>

View File

@@ -0,0 +1,12 @@
import { Binary } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Text to ASCII binary',
path: '/text-to-binary',
description: 'Convert text to its ASCII binary representation and vice versa.',
keywords: ['text', 'to', 'binary', 'converter', 'encode', 'decode', 'ascii'],
component: () => import('./text-to-binary.vue'),
icon: Binary,
createdAt: new Date('2023-10-15'),
});

View File

@@ -0,0 +1,25 @@
import { expect, test } from '@playwright/test';
test.describe('Tool - Text to ASCII binary', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/text-to-binary');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('Text to ASCII binary - IT Tools');
});
test('Text to binary conversion', async ({ page }) => {
await page.getByTestId('text-to-binary-input').fill('it-tools');
const binary = await page.getByTestId('text-to-binary-output').inputValue();
expect(binary).toEqual('01101001 01110100 00101101 01110100 01101111 01101111 01101100 01110011');
});
test('Binary to text conversion', async ({ page }) => {
await page.getByTestId('binary-to-text-input').fill('01101001 01110100 00101101 01110100 01101111 01101111 01101100 01110011');
const text = await page.getByTestId('binary-to-text-output').inputValue();
expect(text).toEqual('it-tools');
});
});

View File

@@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { convertAsciiBinaryToText, convertTextToAsciiBinary } from './text-to-binary.models';
describe('text-to-binary', () => {
describe('convertTextToAsciiBinary', () => {
it('a text string is converted to its ascii binary representation', () => {
expect(convertTextToAsciiBinary('A')).toBe('01000001');
expect(convertTextToAsciiBinary('hello')).toBe('01101000 01100101 01101100 01101100 01101111');
expect(convertTextToAsciiBinary('')).toBe('');
});
it('the separator between octets can be changed', () => {
expect(convertTextToAsciiBinary('hello', { separator: '' })).toBe('0110100001100101011011000110110001101111');
});
});
describe('convertAsciiBinaryToText', () => {
it('an ascii binary string is converted to its text representation', () => {
expect(convertAsciiBinaryToText('01101000 01100101 01101100 01101100 01101111')).toBe('hello');
expect(convertAsciiBinaryToText('01000001')).toBe('A');
expect(convertTextToAsciiBinary('')).toBe('');
});
it('the given binary string is cleaned before conversion', () => {
expect(convertAsciiBinaryToText(' 01000 001garbage')).toBe('A');
});
it('throws an error if the given binary string as no complete octet', () => {
expect(() => convertAsciiBinaryToText('010000011')).toThrow('Invalid binary string');
expect(() => convertAsciiBinaryToText('1')).toThrow('Invalid binary string');
});
});
});

View File

@@ -0,0 +1,22 @@
export { convertTextToAsciiBinary, convertAsciiBinaryToText };
function convertTextToAsciiBinary(text: string, { separator = ' ' }: { separator?: string } = {}): string {
return text
.split('')
.map(char => char.charCodeAt(0).toString(2).padStart(8, '0'))
.join(separator);
}
function convertAsciiBinaryToText(binary: string): string {
const cleanBinary = binary.replace(/[^01]/g, '');
if (cleanBinary.length % 8) {
throw new Error('Invalid binary string');
}
return cleanBinary
.split(/(\d{8})/)
.filter(Boolean)
.map(binary => String.fromCharCode(Number.parseInt(binary, 2)))
.join('');
}

View File

@@ -0,0 +1,42 @@
<script setup lang="ts">
import { convertAsciiBinaryToText, convertTextToAsciiBinary } from './text-to-binary.models';
import { withDefaultOnError } from '@/utils/defaults';
import { useCopy } from '@/composable/copy';
import { isNotThrowing } from '@/utils/boolean';
const inputText = ref('');
const binaryFromText = computed(() => convertTextToAsciiBinary(inputText.value));
const { copy: copyBinary } = useCopy({ source: binaryFromText });
const inputBinary = ref('');
const textFromBinary = computed(() => withDefaultOnError(() => convertAsciiBinaryToText(inputBinary.value), ''));
const inputBinaryValidationRules = [
{
validator: (value: string) => isNotThrowing(() => convertAsciiBinaryToText(value)),
message: 'Binary should be a valid ASCII binary string with multiples of 8 bits',
},
];
const { copy: copyText } = useCopy({ source: textFromBinary });
</script>
<template>
<c-card title="Text to ASCII binary">
<c-input-text v-model:value="inputText" multiline placeholder="e.g. 'Hello world'" label="Enter text to convert to binary" autosize autofocus raw-text test-id="text-to-binary-input" />
<c-input-text v-model:value="binaryFromText" label="Binary from your text" multiline raw-text readonly mt-2 placeholder="The binary representation of your text will be here" test-id="text-to-binary-output" />
<div mt-2 flex justify-center>
<c-button :disabled="!binaryFromText" @click="copyBinary()">
Copy binary to clipboard
</c-button>
</div>
</c-card>
<c-card title="ASCII binary to text">
<c-input-text v-model:value="inputBinary" multiline placeholder="e.g. '01001000 01100101 01101100 01101100 01101111'" label="Enter binary to convert to text" autosize raw-text :validation-rules="inputBinaryValidationRules" test-id="binary-to-text-input" />
<c-input-text v-model:value="textFromBinary" label="Text from your binary" multiline raw-text readonly mt-2 placeholder="The text representation of your binary will be here" test-id="binary-to-text-output" />
<div mt-2 flex justify-center>
<c-button :disabled="!textFromBinary" @click="copyText()">
Copy text to clipboard
</c-button>
</div>
</c-card>
</template>

View File

@@ -1,11 +1,11 @@
import { ArrowsShuffle } from '@vicons/tabler';
import { defineTool } from '../tool';
import { translate } from '@/plugins/i18n.plugin';
export const tool = defineTool({
name: 'Token generator',
name: translate('tools.token-generator.title'),
path: '/token-generator',
description:
'Generate random string with the chars you want: uppercase or lowercase letters, numbers and/or symbols.',
description: translate('tools.token-generator.description'),
keywords: ['token', 'random', 'string', 'alphanumeric', 'symbols', 'number', 'letters', 'lowercase', 'uppercase'],
component: () => import('./token-generator.tool.vue'),
icon: ArrowsShuffle,

View File

@@ -6,4 +6,10 @@ tools:
uppercase: Uppercase (ABC...)
lowercase: Lowercase (abc...)
numbers: Numbers (123...)
symbols: Symbols (!-;...)
symbols: Symbols (!-;...)
length: Length
tokenPlaceholder: 'The token...'
copied: Token copied to the clipboard
button:
copy: Copy
refresh: Refresh

View File

@@ -21,7 +21,7 @@ const [token, refreshToken] = computedRefreshable(() =>
}),
);
const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard' });
const { copy } = useCopy({ source: token, text: t('tools.token-generator.copied') });
</script>
<template>
@@ -51,14 +51,14 @@ const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard'
</div>
</n-form>
<n-form-item :label="`Length (${length})`" label-placement="left">
<n-form-item :label="`${t('tools.token-generator.length')} (${length})`" label-placement="left">
<n-slider v-model:value="length" :step="1" :min="1" :max="512" />
</n-form-item>
<c-input-text
v-model:value="token"
multiline
placeholder="The token..."
:placeholder="t('tools.token-generator.tokenPlaceholder')"
readonly
rows="3"
autosize
@@ -67,10 +67,10 @@ const { copy } = useCopy({ source: token, text: 'Token copied to the clipboard'
<div mt-5 flex justify-center gap-3>
<c-button @click="copy()">
Copy
{{ t('tools.token-generator.button.copy') }}
</c-button>
<c-button @click="refreshToken">
Refresh
{{ t('tools.token-generator.button.refresh') }}
</c-button>
</div>
</c-card>

View File

@@ -0,0 +1,12 @@
import { SortDescendingNumbers } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'ULID generator',
path: '/ulid-generator',
description: 'Generate random Universally Unique Lexicographically Sortable Identifier (ULID).',
keywords: ['ulid', 'generator', 'random', 'id', 'alphanumeric', 'identity', 'token', 'string', 'identifier', 'unique'],
component: () => import('./ulid-generator.vue'),
icon: SortDescendingNumbers,
createdAt: new Date('2023-09-11'),
});

View File

@@ -0,0 +1,23 @@
import { expect, test } from '@playwright/test';
const ULID_REGEX = /[0-9A-Z]{26}/;
test.describe('Tool - ULID generator', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/ulid-generator');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('ULID generator - IT Tools');
});
test('the refresh button generates a new ulid', async ({ page }) => {
const ulid = await page.getByTestId('ulids').textContent();
expect(ulid?.trim()).toMatch(ULID_REGEX);
await page.getByTestId('refresh').click();
const newUlid = await page.getByTestId('ulids').textContent();
expect(ulid?.trim()).not.toBe(newUlid?.trim());
expect(newUlid?.trim()).toMatch(ULID_REGEX);
});
});

View File

@@ -0,0 +1,46 @@
<script setup lang="ts">
import { ulid } from 'ulid';
import _ from 'lodash';
import { computedRefreshable } from '@/composable/computedRefreshable';
import { useCopy } from '@/composable/copy';
const amount = useStorage('ulid-generator-amount', 1);
const formats = [{ label: 'Raw', value: 'raw' }, { label: 'JSON', value: 'json' }] as const;
const format = useStorage<typeof formats[number]['value']>('ulid-generator-format', formats[0].value);
const [ulids, refreshUlids] = computedRefreshable(() => {
const ids = _.times(amount.value, () => ulid());
if (format.value === 'json') {
return JSON.stringify(ids, null, 2);
}
return ids.join('\n');
});
const { copy } = useCopy({ source: ulids, text: 'ULIDs copied to the clipboard' });
</script>
<template>
<div flex flex-col justify-center gap-2>
<div flex items-center>
<label w-75px> Quantity:</label>
<n-input-number v-model:value="amount" min="1" max="100" flex-1 />
</div>
<c-buttons-select v-model:value="format" :options="formats" label="Format: " label-width="75px" />
<c-card mt-5 flex data-test-id="ulids">
<pre m-0 m-x-auto>{{ ulids }}</pre>
</c-card>
<div flex justify-center gap-2>
<c-button data-test-id="refresh" @click="refreshUlids()">
Refresh
</c-button>
<c-button @click="copy()">
Copy
</c-button>
</div>
</div>
</template>

View File

@@ -14,25 +14,18 @@ const { userAgentInfo, sections } = toRefs(props);
<n-grid :x-gap="12" :y-gap="8" cols="1 s:2" responsive="screen">
<n-gi v-for="{ heading, icon, content } in sections" :key="heading">
<c-card h-full>
<n-page-header>
<template #title>
{{ heading }}
</template>
<template v-if="icon" #avatar>
<n-icon size="30" :component="icon" :depth="3" />
</template>
</n-page-header>
<div flex items-center gap-3>
<n-icon size="30" :component="icon" :depth="3" />
<span text-lg>{{ heading }}</span>
</div>
<div mt-5 flex gap-2>
<span v-for="{ label, getValue } in content" :key="label">
<n-tooltip v-if="getValue(userAgentInfo)" trigger="hover">
<template #trigger>
<n-tag type="success" size="large" round :bordered="false">
{{ getValue(userAgentInfo) }}
</n-tag>
</template>
{{ label }}
</n-tooltip>
<c-tooltip v-if="getValue(userAgentInfo)" :tooltip="label">
<n-tag type="success" size="large" round :bordered="false">
{{ getValue(userAgentInfo) }}
</n-tag>
</c-tooltip>
</span>
</div>
<div flex flex-col>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
const optionsA = [
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b', tooltip: 'This is a tooltip' },
{ label: 'Option C', value: 'c' },
];
const valueA = ref('a');
</script>
<template>
<c-buttons-select v-model:value="valueA" :options="optionsA" label="Label: " />
<c-buttons-select v-model:value="valueA" :options="optionsA" label="Label: " label-position="left" mt-2 />
<c-buttons-select v-model:value="valueA" :options="optionsA" label="Label: " label-position="left" mt-2 />
</template>

View File

@@ -0,0 +1,5 @@
import type { CSelectOption } from '../c-select/c-select.types';
export type CButtonSelectOption<T> = CSelectOption<T> & {
tooltip?: string
};

View File

@@ -0,0 +1,59 @@
<script setup lang="ts" generic="T extends unknown">
import type { CLabelProps } from '../c-label/c-label.types';
import type { CButtonSelectOption } from './c-buttons-select.types';
const props = withDefaults(
defineProps<{
options?: CButtonSelectOption<T>[] | string[]
value?: T
size?: 'small' | 'medium' | 'large'
} & CLabelProps >(),
{
options: () => [],
value: undefined,
labelPosition: 'left',
size: 'medium',
},
);
const emits = defineEmits(['update:value']);
const { options: rawOptions, size } = toRefs(props);
const options = computed(() => {
return rawOptions.value.map((option: string | CButtonSelectOption<T>) => {
if (typeof option === 'string') {
return { label: option, value: option };
}
return option;
});
});
const value = useVModel(props, 'value', emits);
function selectOption(option: CButtonSelectOption<T>) {
// @ts-expect-error vue template generic is a bit flacky thanks to withDefaults
value.value = option.value;
}
</script>
<template>
<c-label v-bind="props">
<div class="flex gap-2">
<c-tooltip
v-for="option in options" :key="option.value"
:tooltip="option.tooltip"
>
<c-button
:test-id="option.value"
:size="size"
:type="option.value === value ? 'primary' : 'default'"
@click="selectOption(option)"
>
{{ option.label }}
</c-button>
</c-tooltip>
</div>
</c-label>
</template>

View File

@@ -0,0 +1,20 @@
<script lang="ts" setup>
const data = ref([
{ name: 'John', age: 20 },
{ name: 'Jane', age: 24 },
{ name: 'Joe', age: 30 },
]);
</script>
<template>
<c-table :data="data" mb-2 />
<c-table :data="data" hide-headers mb-2 />
<c-table :data="data" :headers="['age', 'name']" mb-2 />
<c-table :data="data" :headers="['age', { key: 'name', label: 'Full name' }]" mb-2 />
<c-table :data="data" :headers="{ name: 'full name' }" mb-2 />
<c-table :data="data" :headers="['age', 'name']">
<template #age="{ value }">
{{ value }}yo
</template>
</c-table>
</template>

View File

@@ -0,0 +1,4 @@
export type HeaderConfiguration = (string | {
key: string
label?: string
})[] | Record<string, string>;

View File

@@ -0,0 +1,65 @@
<script lang="ts" setup>
import _ from 'lodash';
import type { HeaderConfiguration } from './c-table.types';
const props = withDefaults(defineProps<{ data?: Record<string, unknown>[]; headers?: HeaderConfiguration ; hideHeaders?: boolean; description?: string }>(), { data: () => [], headers: undefined, hideHeaders: false, description: 'Data table' });
const { data, headers: rawHeaders, hideHeaders } = toRefs(props);
const headers = computed(() => {
if (rawHeaders.value) {
if (Array.isArray(rawHeaders.value)) {
return rawHeaders.value.map((value) => {
if (typeof value === 'string') {
return { key: value, label: value };
}
const { key, label } = value;
return {
key,
label: label ?? key,
};
});
}
return _.map(rawHeaders.value, (value, key) => ({
key, label: value,
}));
}
return _.chain(data.value)
.map(row => Object.keys(row))
.flatten()
.uniq()
.map(key => ({ key, label: key }))
.value();
});
</script>
<template>
<div class="relative overflow-x-auto rounded">
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
<thead v-if="!hideHeaders" class="bg-#ffffff uppercase text-gray-700 dark:bg-#333333 dark:text-gray-400">
<tr>
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
{{ header.label }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(row, i) in data" :key="i" border-b="1px solid dark:#282828 #efeff5" class="bg-white dark:bg-#232323"
:class="{
'important:border-b-none': i === data.length - 1,
}"
>
<td v-for="header in headers" :key="header.key" class="px-6 py-4">
<slot :name="header" :row="row" :headers="headers" :value="row[header.key]">
{{ row[header.key] }}
</slot>
</td>
</tr>
</tbody>
</table>
</div>
</template>

View File

@@ -1,3 +1,7 @@
<script lang="ts" setup>
const positions = ['top', 'bottom', 'left', 'right'] as const;
</script>
<template>
<div>
<c-tooltip>
@@ -14,4 +18,18 @@
Hover me
</c-tooltip>
</div>
<div mt-5>
<h2>Tooltip positions</h2>
<div class="flex flex-wrap gap-4">
<div v-for="position in positions" :key="position">
<c-tooltip :position="position" :tooltip="`Tooltip ${position}`">
<c-button>
{{ position }}
</c-button>
</c-tooltip>
</div>
</div>
</div>
</template>

View File

@@ -1,22 +1,30 @@
<script setup lang="ts">
const props = withDefaults(defineProps<{ tooltip?: string }>(), { tooltip: '' });
const { tooltip } = toRefs(props);
const props = withDefaults(defineProps<{ tooltip?: string; position?: 'top' | 'bottom' | 'left' | 'right' }>(), {
tooltip: undefined,
position: 'top',
});
const { tooltip, position } = toRefs(props);
const targetRef = ref();
const isTargetHovered = useElementHover(targetRef);
</script>
<template>
<div class="relative" inline-block>
<div relative inline-block>
<div ref="targetRef">
<slot />
</div>
<div
class="absolute bottom-100% left-50% z-10 mb-5px whitespace-nowrap rounded bg-black px-12px py-6px text-sm text-white shadow-lg transition transition transition-duration-0.2s -translate-x-1/2"
v-if="tooltip || $slots.tooltip"
class="absolute z-10 whitespace-nowrap rounded bg-black px-12px py-6px text-sm text-white shadow-lg transition transition transition-duration-0.2s"
:class="{
'op-0 scale-0': isTargetHovered === false,
'op-100 scale-100': isTargetHovered,
'bottom-100% left-50% -translate-x-1/2 mb-5px': position === 'top',
'top-100% left-50% -translate-x-1/2 mt-5px': position === 'bottom',
'right-100% top-50% -translate-y-1/2 mr-5px': position === 'left',
'left-100% top-50% -translate-y-1/2 ml-5px': position === 'right',
}"
>
<slot

View File

@@ -20,5 +20,6 @@ export default defineConfig({
shortcuts: {
'pretty-scrollbar': 'scrollbar scrollbar-rounded scrollbar-thumb-color-gray-300 scrollbar-track-color-gray-100 dark:scrollbar-thumb-color-#424242 dark:scrollbar-track-color-#686868',
'divider': 'h-1px bg-current op-10',
'bg-surface': 'bg-#ffffff dark:bg-#232323',
},
});