Compare commits

..

294 Commits

Author SHA1 Message Date
Corentin Thomasset
facc0b5d9e chore(deps): updated minor deps version 2023-08-26 18:20:24 +02:00
Corentin THOMASSET
81bfe57cb8 feat(new tool): text diff and comparator (#588)
* feat(new tool): text diff and comparator

* chore(ci): increased memory in CI
2023-08-26 16:43:47 +02:00
renovate[bot]
a9cd91ca9c chore(deps): update dependency @antfu/eslint-config to ^0.41.0 (#585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-26 16:28:31 +02:00
Corentin THOMASSET
06c35472d3 doc(hosting): added cloudron in the other hosting solutions section (#589) 2023-08-26 14:18:12 +00:00
renovate[bot]
f3e14fc18f chore(deps): update dependency typescript to ~5.2.0 (#587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-26 16:17:19 +02:00
Corentin Thomasset
2274766a8f chore(version): release 2023.08.21-6f93cba 2023-08-22 01:02:24 +02:00
Corentin Thomasset
a346175d24 docs(changelog): update changelog for 2023.08.21-6f93cba 2023-08-22 01:02:24 +02:00
Corentin THOMASSET
6f93cba3da feat(copy): support legacy copy to clipboard for older browser (#581) 2023-08-21 23:00:20 +00:00
renovate[bot]
76b2761d62 chore(deps): switched to fucking typescript v5 (#501)
* chore(deps): update dependency typescript to v5

* chore(deps): switched to fucking typescript v5

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-08-21 19:57:59 +00:00
renovate[bot]
6ff9a01cc8 chore(deps): update dependency @antfu/eslint-config to ^0.40.0 (#552)
* chore(deps): update dependency @antfu/eslint-config to ^0.40.0

* chore(deps): updated eslint

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-08-21 18:27:08 +00:00
renovate[bot]
a2b9b157e5 chore(deps): update dependency prettier to v3 (#564)
* chore(deps): update dependency prettier to v3

* chore(deps): updated prettier

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-08-21 18:02:54 +00:00
renovate[bot]
144f86e2dc chore(deps): removed @typescript-eslint/parser (#563)
* chore(deps): update dependency @typescript-eslint/parser to v6

* chore(deps): removed @typescript-eslint/parser

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-08-21 17:18:04 +00:00
renovate[bot]
0f1f6590c5 chore(deps): removed ts-pattern (#565)
* fix(deps): update dependency ts-pattern to v5

* chore(deps): removed ts-pattern

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-08-21 17:06:09 +00:00
renovate[bot]
2bcb77a9f9 fix(deps): update dependency sql-formatter to v12 (#520)
* fix(deps): update dependency sql-formatter to v12

* fix(sql-prettify): corrected types

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-08-21 18:34:15 +02:00
Corentin THOMASSET
c58d6e3423 feat(new tool): string obfuscator (#575) 2023-08-16 21:43:45 +00:00
Corentin Thomasset
f235dcd6c1 chore(version): release 2023.08.16-9bd4ad4 2023-08-16 14:19:08 +02:00
Corentin Thomasset
ba2c589f0f docs(changelog): update changelog for 2023.08.16-9bd4ad4 2023-08-16 14:19:08 +02:00
renovate[bot]
9bd4ad4dfd chore(deps): update dependency vitest to ^0.34.0 (#562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 16:15:18 +02:00
renovate[bot]
5e12991bcd fix(deps): update dependency uuid to v9 (#566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 15:12:27 +02:00
renovate[bot]
65a9474078 chore(deps): update dependency node to v18.17.1 (#560)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 01:10:53 +02:00
renovate[bot]
85cc7a8447 chore(deps): update dependency unocss to ^0.55.0 (#561)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-09 22:31:31 +00:00
renovate[bot]
4268e255de chore(deps): update dependency @unocss/eslint-config to ^0.55.0 (#553)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-09 22:22:15 +00:00
renovate[bot]
d1c888019b chore(deps): update dependency @intlify/unplugin-vue-i18n to ^0.12.0 (#526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 00:11:59 +02:00
renovate[bot]
7924456cec fix(deps): update dependency mathjs to v11 (#519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 00:11:30 +02:00
renovate[bot]
99bc84c37e chore(deps): update docker/login-action action to v2 (#512)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 00:10:31 +02:00
renovate[bot]
ea0f27cf4c fix(deps): update dependency @vueuse/router to v10 (#516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 00:10:19 +02:00
renovate[bot]
cd5a503fc0 chore(deps): update dependency jsdom to v22 (#499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-10 00:09:39 +02:00
Corentin THOMASSET
86e964a274 fix(copy): prevent shorthand copy if source is present in useCopy (#559)
* fix(copy): prevent shorthand copy if source is present in useCopy

* refactor(copy): normalized copy usage
2023-08-09 22:07:44 +00:00
Marvin
7b6232a151 feat(Case Converter): Add lowercase and uppercase (#534)
* feat(case converter): add uppercase and lowercase

* (case converter) correctly use stripRegexp

* style: lint fix

* feat(ui): added c-select in the ui lib (#550)

* feat(ui): added c-select in the ui lib

* refactor(ui): switched n-select to c-select

* feat(new tool): emoji picker (#551)

* chore(deps): update dependency @vitejs/plugin-vue to v4 (#496)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore(deps): update dependency @vitejs/plugin-vue-jsx to v3 (#497)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* refactor(case converter): using nocase to convert to upper and lower case

* refactor(case converter): config based case changes

---------

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-09 23:45:21 +02:00
Corentin THOMASSET
56d74d07a8 fix(c-lib): hide component library shortcut link in non-dev (#557) 2023-08-09 20:58:15 +00:00
Corentin THOMASSET
e5d0ba7073 fix(emoji picker): fix copy button (#556) 2023-08-09 20:54:49 +00:00
renovate[bot]
1a602365be chore(deps): update dependency @vitejs/plugin-vue-jsx to v3 (#497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-08 22:25:59 +02:00
renovate[bot]
a2494219a8 chore(deps): update dependency @vitejs/plugin-vue to v4 (#496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-08 22:25:59 +02:00
Corentin THOMASSET
93f7cf0e98 feat(new tool): emoji picker (#551) 2023-08-08 22:25:59 +02:00
Corentin THOMASSET
dfa1ba8554 feat(ui): added c-select in the ui lib (#550)
* feat(ui): added c-select in the ui lib

* refactor(ui): switched n-select to c-select
2023-08-07 15:30:00 +00:00
renovate[bot]
6498c9b0fa chore(deps): update dependency vite-plugin-pwa to ^0.16.0 (#488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-05 16:29:58 +02:00
renovate[bot]
f40d7ecddf chore(deps): update dependency vite to v4 (#503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-29 20:35:23 +02:00
Corentin THOMASSET
2e28c5073e chore(ci): e2e against vercel deployement (#518) 2023-06-29 20:27:35 +02:00
renovate[bot]
d12dd40841 fix(deps): update dependency @vueuse/head to v1 (#515)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-29 20:20:28 +02:00
Corentin THOMASSET
cf382b5a10 chore(e2e): execute e2e against built app (#511) 2023-06-29 18:48:51 +02:00
renovate[bot]
01525838e0 chore(deps): update github/codeql-action action to v2 (#513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-29 08:57:10 +02:00
renovate[bot]
38cb61da4d chore(deps): update node.js to v18 (#514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-28 08:35:45 +02:00
Corentin THOMASSET
354aed6e6f chore(deps): switched from vite-plugin-md to vite-plugin-vue-markdown (#510) 2023-06-27 18:39:20 +00:00
renovate[bot]
6b8682fd23 chore(deps): update dependency workbox-window to v7 (#509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-27 20:29:41 +02:00
renovate[bot]
9e8349dbc4 chore(deps): update dependency vite-svg-loader to v4 (#508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-27 20:13:06 +02:00
renovate[bot]
f44050742d chore(deps): update dependency typescript to ~4.9.0 (#481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-27 20:08:12 +02:00
renovate[bot]
b0d9a3e6c7 chore(deps): update dependency vue-tsc to ^0.40.0 (#490)
* chore(deps): update dependency vue-tsc to ^0.40.0

* chore(deps): upgraded vue-tsc dependency

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-06-27 17:59:05 +00:00
Corentin THOMASSET
30f88fc6a8 refactor(navbar): consistent spacing in navbar buttons (#507) 2023-06-27 16:47:32 +00:00
Corentin THOMASSET
72c98a3c5e refactor(ui): remove n-text (#506) 2023-06-25 13:49:43 +00:00
Corentin THOMASSET
05ea545475 refactor(ui): replaced some n-input to c-input (#505) 2023-06-25 13:00:50 +00:00
Corentin THOMASSET
5c3bebfe62 chore(deps): updated unplugin-auto-import (#504) 2023-06-25 09:45:56 +00:00
renovate[bot]
8df7cd0f19 chore(deps): removed start-server-and-test dependency
* chore(deps): update dependency start-server-and-test to v2

* chore(deps): removed start-server-and-test dependency

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-06-25 08:34:36 +00:00
Corentin THOMASSET
a9c7b89193 feat(new-tool): password strength analyzer (#502) 2023-06-25 08:26:29 +00:00
renovate[bot]
6bda2caa04 chore(deps): update dependency c8 to v8 (#498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-25 10:02:46 +02:00
renovate[bot]
994a1c3401 chore(deps): update dependency @types/jsdom to v21 (#495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 18:55:46 +02:00
renovate[bot]
05edaf423c chore(deps): update node.js to v16.20.1 (#491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 18:54:36 +02:00
renovate[bot]
8c72e692a7 fix(deps): update dependency country-code-lookup to ^0.1.0 (#493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 18:49:54 +02:00
renovate[bot]
cec9dea9e0 fix(deps): update dependency @vueuse/head to ^0.9.0 (#492)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 18:48:42 +02:00
renovate[bot]
49eacea195 chore(deps): update dependency vitest to ^0.32.0 (#489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 18:47:57 +02:00
renovate[bot]
3f7d469e9f chore(deps): update actions/checkout action to v3 (#494)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-24 18:46:01 +02:00
renovate[bot]
5f2190887d chore(deps): update dependency unplugin-vue-components to ^0.25.0 (#484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-23 22:19:08 +02:00
renovate[bot]
6cb0845336 chore(deps): update dependency unplugin-auto-import to ^0.16.0 (#483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-23 22:18:44 +02:00
Corentin Thomasset
12d9e5d377 fix(i18n): fallback for demo i18n 2023-06-23 21:57:08 +02:00
Corentin Thomasset
e29b258e90 feat(new-tool): yaml to toml 2023-06-23 21:47:23 +02:00
Corentin Thomasset
ea50a3fc65 feat(new-tool): json to toml 2023-06-23 21:47:23 +02:00
Corentin Thomasset
746e5bdccc feat(new-tool): toml to yaml 2023-06-23 21:47:23 +02:00
Corentin Thomasset
c7d4f112c0 feat(new-tool): toml to json 2023-06-23 21:47:23 +02:00
Marvin
9125dcf9c6 refactor(json-viewer): input monospace font (#485) 2023-06-23 19:38:34 +00:00
renovate[bot]
38710dce56 chore(deps): update dependency unocss to ^0.53.0 (#482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-23 21:30:45 +02:00
renovate[bot]
282cfc4c4b chore(deps): update dependency @unocss/eslint-config to ^0.53.0 (#478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-23 16:59:18 +02:00
Marvin
ba4876d0d5 refacor(transformers): use monospace font for JSON and SQL text areas (#476)
* feat(c-input): Add monospace prop

* feat: use monospace input for json and sql inputs
2023-06-23 16:51:52 +02:00
renovate[bot]
363c2e47e6 chore(deps): added renovate.json (#477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-23 16:49:03 +02:00
Marvin
9526ed8324 fix(typos): fixed more typos & uppercase JSON (#475)
* fix(About): typo

* uppercase json & typos
2023-06-23 15:23:47 +02:00
Marvin
7068610438 fix(about): typos and wording (#474)
* fix(About): typos and wording

* Update src/pages/About.vue

---------

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2023-06-21 19:45:48 +00:00
Corentin THOMASSET
847323ccba docs(ide): updated vscode extensions settings (#472) 2023-06-19 21:49:40 +00:00
Corentin THOMASSET
1b038c7826 chore(i18n): tool scoped locales (#471) 2023-06-19 21:14:44 +00:00
Corentin Thomasset
ec4c533718 feat(command-palette): random tool action 2023-06-19 21:51:25 +02:00
Seb
63045951e1 feat(config): allow app to run in a subfolder via BASE_URL (#461)
* fix: allow hosting in subfolder via BASE_URL

* Import baseUrl from config

* Revert "Import baseUrl from config"

This reverts commit 73c867ebc8.

* feat(config): fallback baseUrl to /

* feat(config): fallback baseUrl to /

---------

Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-06-19 18:42:29 +02:00
Corentin THOMASSET
c4cec9e18f fix(mime-types): typos (#470) 2023-06-19 16:09:03 +00:00
Corentin THOMASSET
bcb98b359c refactor(search): command palette design (#463) 2023-06-19 00:35:50 +02:00
Corentin Thomasset
732da08157 chore(wysiwyg-editor): update tiptap dependencies 2023-06-19 00:08:07 +02:00
Seb
b9406a492d feat(new-tool): percentage calculator (#456)
* feat(new tool): percentage calculator

* Apply suggestions from code review

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>

---------

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2023-06-18 18:24:39 +02:00
Corentin Thomasset
69f0bd079f feat(new-tool): json to csv converter 2023-06-18 18:10:06 +02:00
Corentin Thomasset
4cbd7ac145 fix(sonar): took down minor sonar warning 2023-06-18 14:56:52 +02:00
Corentin Thomasset
ebfb872fae chore(i18n): setup i18n plugin config 2023-06-18 14:33:58 +02:00
jmmanzano
a6bbeaebd8 feat(new tool): xml formatter (#457)
* feat(new tool): xml formatter

* feat(xml-formatter): added happy path e2e tests

* refactor(xml-formatter): improved unit tests

* refactor(xml-formatter): add better suitable icon

* feat(xml-formatter): added happy path e2e tests

* feat(xml-formatter): registered xml as syntax highlighter

* chore(auto-import): removed unused NSpace

---------

Co-authored-by: Corentin Thomasset <corentin.thomasset74@gmail.com>
2023-06-18 10:27:26 +00:00
myztillx
f771e7a99f feat(chmod-calculator): added symbolic representation (#455) 2023-06-18 11:01:54 +02:00
Seb
cf7b1f000a feat(enhancement): use system dark mode (#458)
* Use prefers-color-scheme

* Remove theme store
2023-06-18 10:59:22 +02:00
Corentin Thomasset
1e2a35b892 refactor(c-input-text): force usage of props with default 2023-06-11 21:27:40 +02:00
Corentin Thomasset
45c2474279 refactor(naming): prevent auto import conflicts for git memo 2023-06-10 17:37:38 +02:00
Corentin Thomasset
fe61f0f2f2 refactor(imports): removed unnecessary imports to vue 2023-06-10 17:24:10 +02:00
David Nguyen
93799af83c chore(config): netlify deployment support (#443)
* Create netlify.toml

* Update netlify.toml

---------

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2023-06-10 17:02:08 +02:00
Corentin Thomasset
962a6d6ec4 chore(ci): shard e2e tests 2023-06-10 16:59:54 +02:00
Corentin Thomasset
d2956b66fe feat(phone-parser): searchable country code select 2023-06-10 16:43:03 +02:00
DawnElixir
105b21badc fix(readme): typo
Fix readme typo
2023-05-31 22:55:44 +02:00
Corentin Thomasset
33c9b6643f chore(lint): switched to a better lint config 2023-05-28 23:29:14 +02:00
Corentin Thomasset
4d2b037dbe refactor(ui): removed all n-space 2023-05-27 17:53:13 +02:00
Corentin Thomasset
34d8e5ce2c feat(new tool): camera screenshot and recorder 2023-05-17 00:34:10 +02:00
Corentin Thomasset
8515c24264 chors(deps): updated vueuse dependency version 2023-05-15 17:21:59 +02:00
cgoIT
0b20f1c16a feat(base64-string-converter): switch to encode and decode url safe base64 strings (#392)
* feat(base64-string-converter): switch to encode and decode url safe

* feat(base64-string-converter): changes based on review comments, use config object instead of boolean argument.

* feat(base64-string-converter): fix validation, add option to watch additional refs for changes which interfere with validation rules
2023-05-15 14:35:44 +02:00
cgoIT
8c92d56318 fix(ipv4-range-expander): calculate correct for ip addresses where the first octet is lower than 128 (#405) 2023-05-15 10:23:16 +02:00
Corentin Thomasset
7aed9c56fd fix(ipv4-converter): removed readonly on input 2023-05-15 10:00:55 +02:00
Corentin Thomasset
f7fc779e63 refactor(ui): replaced some n-input with c-input-text 2023-05-15 01:31:22 +02:00
Corentin Thomasset
b3b6b7c46b chore(version): release 2023.05.14-77f2efc 2023-05-14 22:48:20 +02:00
Corentin Thomasset
141c12455e docs(changelog): update changelog for 2023.05.14-77f2efc 2023-05-14 22:48:20 +02:00
Corentin Thomasset
77f2efc0b9 refactor(ui): replaced some n-input with c-input-text 2023-05-14 22:30:23 +02:00
Corentin Thomasset
aad8d84e13 ui-lib(new-component): added text input component in the c-lib 2023-05-14 22:30:23 +02:00
Corentin Thomasset
401f13f7e3 ui-lib(button): size variants 2023-05-14 22:30:23 +02:00
Corentin THOMASSET
edae4c6915 chore(issues): updated new tool request issue template 2023-05-13 22:09:55 +02:00
Corentin Thomasset
a43c546e34 fix(phone-parser): use default country code 2023-05-07 13:25:33 +02:00
cgoIT
83a7b3bae9 feat(list-converter): a small converter who deals with column based data and do some stuff with it (#387)
* feat(list-converter): a small converter who deals with column based data and do some stuff with it

* Update src/tools/list-converter/index.ts

* Update src/tools/list-converter/index.ts

* Update src/tools/list-converter/index.ts

---------

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>

fix(list-format): fix e2e
2023-05-07 13:25:25 +02:00
Corentin Thomasset
ce3150c65d feat(new tool): phone parser and normalizer 2023-05-02 13:57:39 +02:00
Corentin Thomasset
3f6c8f0edd fix(home): prevent weird blue border on card 2023-05-01 13:44:30 +02:00
Corentin Thomasset
daf2cf0285 chore(version): release 2023.04.23-92bd835 2023-04-23 22:44:35 +02:00
Corentin Thomasset
b7aaea1b58 docs(changelog): update changelog for 2023.04.23-92bd835 2023-04-23 22:44:35 +02:00
Corentin Thomasset
92bd83536f feat(ui-lib): demo pages for c-lib components 2023-04-23 22:43:06 +02:00
Corentin Thomasset
e88c1d5f2c fix(ts): cleaned legacy typechecking warning 2023-04-23 17:11:04 +02:00
Corentin Thomasset
362f2fa280 feat(new-tool): diff of two json objects 2023-04-23 15:24:20 +02:00
Corentin Thomasset
61ece2387f refactor(ui-lib): prevent c-button to shrink 2023-04-20 21:03:20 +02:00
Corentin Thomasset
f080933d2a refactor(ui): replaced naive ui cards with custom ones 2023-04-20 20:57:38 +02:00
Corentin Thomasset
bb32513bd3 refactor(clean): removed unused lodash import 2023-04-19 22:58:07 +02:00
Corentin Thomasset
c311e3824d fix(mac-address-lookup): added copy handler on button click 2023-04-19 22:56:50 +02:00
Corentin Thomasset
74073f5038 refactor(clean): removed useless br tags 2023-04-19 22:50:02 +02:00
Corentin Thomasset
c45bce36f9 refactor(ui): getting ride of naive ui buttons 2023-04-19 22:33:22 +02:00
cgoIT
df989e24b3 feat(ipv4-range-expander): expands a given IPv4 start and end address to a valid IPv4 subnet (#366)
* feat(ipv4-range-expander): expands a given IPv4 start and end address to a valid IPv4 subnet

* feat(ipv4-range-expander): remove old component copyable-ip-like.vue

* feat(ipv4-range-expander): fix sonar findings

* feat(ipv4-range-expander): changes due to review

* feat(ipv4-range-expander): only show n-alert if both ipv4 addresses are valid
2023-04-19 20:30:45 +02:00
Corentin Thomasset
6d2202597c feat(date converter): auto focus main input 2023-04-19 13:07:24 +02:00
Corentin Thomasset
c68a1fd713 chore(version): release 2023.04.14-dbad773 2023-04-14 21:09:36 +02:00
Corentin Thomasset
46b1a07213 docs(changelog): update changelog for 2023.04.14-dbad773 2023-04-14 21:09:36 +02:00
Corentin Thomasset
dbad7730f9 chore(release): create a github release on new version 2023-04-14 21:08:43 +02:00
Corentin Thomasset
85cb0ffabd chore(version): reset CHANGELOG content to support new format 2023-04-14 18:34:23 +02:00
Corentin Thomasset
8355bd2ae4 feat(new-tool): http status codes 2023-04-14 09:04:49 +02:00
Corentin Thomasset
6fb4994603 refactor(uuid-generator): prevent NaN in quantity 2023-04-13 23:36:25 +02:00
Corentin Thomasset
7d7cc99866 chore(version): release v2023.04.13-dce9ff9 2023-04-13 01:09:14 +02:00
Corentin Thomasset
dce9ff91e2 feat(cd): git version tag pushed to docker 2023-04-13 01:04:48 +02:00
Corentin Thomasset
80401b6405 refactor(404): simplified 404 page 2023-04-13 00:03:08 +02:00
Corentin Thomasset
fd9ab59172 refactor(date-converter): improved ux and layout 2023-04-12 23:41:09 +02:00
Corentin Thomasset
5fa811a583 chore(deps): updated dependencies 2023-04-12 20:35:44 +02:00
Corentin Thomasset
c0a89131dd feat(new-tool): yaml and json converters 2023-04-10 22:38:35 +02:00
Corentin Thomasset
05f06f6a07 refactor(dx): generic data transformer 2023-04-10 16:42:06 +02:00
Corentin Thomasset
9fa4c26929 refactor(config): removed new tools env config 2023-04-10 13:11:10 +02:00
Corentin Thomasset
9d639edf2d refactor(lint): auto fix lint 2023-04-10 13:01:40 +02:00
cgoIT
a1e983538c feat(new-tool): generate ula based on timestamp and mac address (#344)
* feat(ipv6-ula-generator): new tool: generate ula based on timestamp and mac address

This new tool generates a random unique ula based on the current timestamp and the provided mac address. An ULA is your "secondary" IPV6-Address only for internal use.
It can also be used as a backup address if your provider gets offline and your Prefix-IPs are not longer valid.Also you can create the most of your internal firewall rules based on your ULAs.

* feat(ipv6-ula-generator): changes requested by review

* Update src/tools/ipv6-ula-generator/index.ts

* Update src/tools/ipv6-ula-generator/ipv6-ula-generator.vue

---------

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2023-04-09 21:05:26 +02:00
cgoIT
1d7f8b9a8c feat(new-tool): generate ula based on timestamp and mac address (#344)
* feat(ipv6-ula-generator): new tool: generate ula based on timestamp and mac address

This new tool generates a random unique ula based on the current timestamp and the provided mac address. An ULA is your "secondary" IPV6-Address only for internal use.
It can also be used as a backup address if your provider gets offline and your Prefix-IPs are not longer valid.Also you can create the most of your internal firewall rules based on your ULAs.

* feat(ipv6-ula-generator): changes requested by review

* Update src/tools/ipv6-ula-generator/index.ts

* Update src/tools/ipv6-ula-generator/ipv6-ula-generator.vue

---------

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2023-04-09 20:58:27 +02:00
Corentin Thomasset
ec7cb9351c feat(test): added e2e tests 2023-04-09 18:16:22 +02:00
Corentin Thomasset
ebfdb64fde chore(cd): don't run nightly release if their is no new commits 2023-04-09 15:27:58 +02:00
cgoIT
d2a2686705 fix(mac-address-lookup): fix typo in mac-address-lookup tool (#345) 2023-04-09 14:03:17 +02:00
Corentin Thomasset
d7a503b4ae feat(new-tool): ipv4 address converter 2023-04-08 22:09:22 +02:00
Corentin Thomasset
28145e0ffe feat(new-tool): ipv4 address converter 2023-04-08 21:13:14 +02:00
cgoIT
8930e139b2 fix(roman-numeral-converter): input validation and feedback (#332)
* fix(roman-numeral-converter):  checks for valid input and conversion enhancements

Validates if numeral values are between 1 and 3999999.
Validates if a roman number is valid.

* fix(roman-numeral-converter): optimize logic for copy button

* fix(roman-numeral-converter): changes due to review
2023-04-08 19:33:33 +02:00
Corentin Thomasset
076df11024 feat(new-tool): mac address lookup 2023-04-07 00:24:49 +02:00
Corentin Thomasset
1060652590 chore(cd): docker nightly releases 2023-04-06 21:44:11 +02:00
Corentin Thomasset
f5c865b278 chore(cd): docker nightly releases 2023-04-06 21:05:56 +02:00
Corentin Thomasset
001031b7b5 feat(css): added unocss with attributify preset 2023-04-06 20:35:37 +02:00
Corentin Thomasset
4ccd73c2d1 feat(dx): auto resolve vue components 2023-04-06 19:08:28 +02:00
Corentin Thomasset
2293f63a79 fix(ci): prevent ci from running twice in pr 2023-04-06 18:56:31 +02:00
Corentin Thomasset
0161395585 feat(dx): auto import vite plugin 2023-04-06 18:53:31 +02:00
Corentin Thomasset
4d011f15a1 feat(benchmark-builder): compute gap and export to bulet list 2023-04-06 14:00:54 +02:00
Corentin Thomasset
4a1afb2b69 feat(benchmark-builder): compute gap and export to bulet list 2023-04-06 13:33:51 +02:00
cgoIT
f350dc19aa feat(new-tool): add new tool user agent parser (#329)
* fix(docker-run-to-docker-compose-converter): use different version of converter which suppports more options and is mor failsafe

* chore(docker-run-to-docker-compose-converter): add pnpm-lock.yaml again which was accidently removed in last commit

* chore(docker-run-to-docker-compose-converter): add fixed version of composerize-ts

* chore(user-agent-parser): changes requested by code review

* chore(user-agent-parser): some more changes requested by code review
2023-04-06 10:28:12 +02:00
Corentin Thomasset
f3480fe560 refactor(qr-code): multiline input 2023-04-06 00:55:39 +02:00
Corentin Thomasset
f68e859c20 refactor(docker-run-converter): improve error handling 2023-04-06 00:16:32 +02:00
cgoIT
4872d71165 refactor(docker-run-converter): improved converter
* fix(docker-run-to-docker-compose-converter): use different version of converter which suppports more options and is mor failsafe

* chore(docker-run-to-docker-compose-converter): add pnpm-lock.yaml again which was accidently removed in last commit

* chore(docker-run-to-docker-compose-converter): add fixed version of composerize-ts
2023-04-06 00:10:19 +02:00
Corentin Thomasset
fb8a3a0fee refactor(tool): better new tool logic 2023-04-05 23:30:44 +02:00
Corentin Thomasset
20282987e3 feat(new-tool): simple benchmark calculator 2023-04-05 23:07:13 +02:00
Corentin Thomasset
ff5f38bec6 feat(new-tool): simple benchmark calculator 2023-04-05 23:06:05 +02:00
Corentin Thomasset
6e84ea4061 feat(new-tool): simple benchmark calculator 2023-04-05 22:57:58 +02:00
Corentin Thomasset
004cb83719 chore(clean): removed extra console.log 2023-04-05 08:47:54 +02:00
Corentin Thomasset
aa4dc0418e feat(plausible): disable plausible instance for self hosted instance 2023-04-04 19:01:59 +02:00
Corentin Thomasset
a599528044 fix(otp-generator): editable secret 2023-04-04 12:55:02 +02:00
Radu Ursache
b3390f6ff7 chore(docker): fixed the docker-run command in README (#323) 2023-03-31 16:53:32 +02:00
Corentin Thomasset
dee5586bf5 fix(docker): consistent casing in Dockerfile 2023-03-31 01:10:00 +02:00
Corentin Thomasset
5281824b5d fix(otp-generator): better computation of token 2023-03-31 01:01:44 +02:00
Corentin Thomasset
15cb03347c feat(menu): collapsible category 2023-03-30 23:51:23 +02:00
Corentin Thomasset
24ba0ff5fa feat(menu): collapsible category 2023-03-30 23:48:47 +02:00
Corentin Thomasset
849981d1ec refactor(issue): better issue template 2023-03-30 21:25:39 +02:00
Corentin Thomasset
3b625fd473 feat(json-prettify): sort keys 2023-03-29 23:33:30 +02:00
Corentin Thomasset
863c8d0f6a refactor(new-tool-script): added new tool creation script in package.json 2023-03-29 22:43:51 +02:00
Corentin Thomasset
9b4f7727f2 refactor(uuid-generator): now using computedRefreshable 2023-03-29 21:13:49 +02:00
Corentin Thomasset
cf16cb195d refactor(token-generator): now using computedRefreshable 2023-03-29 21:12:20 +02:00
Corentin Thomasset
f6237376e1 refactor(port-generator): now using computedRefreshable 2023-03-29 21:05:21 +02:00
Corentin Thomasset
165dc93f83 feat(rsa-key-pair-generator): refresh certs button 2023-03-29 20:54:11 +02:00
Corentin Thomasset
161a21f285 feat(config): hide the banner by default 2023-03-29 19:55:40 +02:00
Corentin Thomasset
972ffe6f69 fix(docker): nginx config to redirect to index.html 2023-03-29 00:15:56 +02:00
Corentin Thomasset
c339ab3551 feat(new-tool): IPv4 subnet calculator 2023-03-28 23:49:54 +02:00
Corentin Thomasset
47948dd343 refactor(import): removed unused imports 2023-03-28 23:49:54 +02:00
marvin-j97
91369cb238 refactor(html-editor): use correct letter casing (#292) 2023-03-27 17:38:37 +02:00
cgoIT
ffb99579ba feat(new-tool): add a docker run to docker compose converter (#291)
* Integrate composerize (https://github.com/magicmark/composerize) into it-tools

* Fix Prettier errors

* Fix Prettier errors

* Fix code according to suggestions

* Added syntax highlighting to TextareaCopyable component, some ui adjustments in docker-run-to-docker-compose-converter.vue
2023-03-27 17:31:13 +02:00
Corentin Thomasset
f512d09227 fix(rsa-key-pair-generator): correct prime web worker url 2023-03-27 00:17:36 +02:00
Corentin Thomasset
7c40539ef9 refactor(otp-generator): coerce lowercase secret characters to uppercase 2023-03-26 22:43:56 +02:00
Corentin Thomasset
f3b1863f09 feat(new-tool): html wysiwyg editor 2023-03-26 20:21:00 +02:00
Corentin Thomasset
b1d6bfd2dc refactor(otp-generator): no more barcode color invertion 2023-03-26 16:15:37 +02:00
Corentin Thomasset
8787ce72ab fix(roman-converter): replaced incorrect default value 2023-03-24 18:35:37 +01:00
Corentin Thomasset
dab8d63390 refactor(readme): better headings levels 2023-03-24 08:42:28 +01:00
Corentin Thomasset
96aaa35ea2 docs(docker): added self hosting command in README 2023-03-23 22:47:14 +01:00
Benny
519b169336 chore(docker): arm64 and amd64 as supported platform (#271) 2023-03-23 21:48:07 +01:00
Corentin Thomasset
3bc1f0d1fa chore(docker): it-tools docker container 2023-03-23 18:54:51 +01:00
Corentin Thomasset
b519cc9574 chore(deps): updated dependencies versions 2023-03-19 11:59:43 +01:00
Corentin Thomasset
30e9149d61 feat(new-tool): RSA key pair generator 2023-03-10 18:16:55 +01:00
marvin-j97
7958e2e075 refactor(branding): correct branding for twitter & github (#268) 2023-03-08 18:09:53 +01:00
Corentin Thomasset
61024279b5 fix(about): correct git branch in package.json url 2023-03-08 13:48:52 +01:00
Corentin Thomasset
ad202bd372 revert(pwa): fallback to autoUpdate 2023-03-01 23:35:17 +01:00
Corentin Thomasset
400654b6b1 chore(pwa): close update notification on update 2023-03-01 23:24:17 +01:00
Corentin Thomasset
3351b70c1a refactor(sponsor): updated support link to buy me a coffe 2023-03-01 22:15:51 +01:00
Corentin Thomasset
53ce079dff refactor(pwa): prompt for pwa update 2023-03-01 22:00:29 +01:00
Corentin Thomasset
a771346250 refactor(style): improved dark mode colors 2023-02-21 23:14:27 +01:00
Corentin Thomasset
a312dedf65 fix(jwt-parser): prevent [object Object] value 2023-02-21 22:52:08 +01:00
Corentin Thomasset
0ddf18f4b5 feat(new-tool): String to NATO alphabet 2023-02-15 00:45:20 +01:00
Corentin Thomasset
9634f5d9a8 fix(case-converter): allowed diacritics 2023-02-10 22:59:44 +01:00
Corentin Thomasset
e6c0445684 fix(case-converter): allowed diacritics 2023-02-10 22:53:10 +01:00
Corentin Thomasset
32f87f3fca refactor(tool-card): enforce description height 2023-02-10 22:20:50 +01:00
Corentin Thomasset
a3b1cb5737 refactor(tool-card): enforce description height 2023-02-10 22:13:08 +01:00
Corentin Thomasset
6fe4b5ac60 feat(new-tool): slugify string 2023-02-10 22:06:32 +01:00
Corentin Thomasset
1a3f0a135d refactor(keycode-info): wording 2023-02-06 23:26:40 +01:00
Corentin Thomasset
072083832d chore(release): 2.19.0 2023-02-06 23:14:52 +01:00
Corentin Thomasset
c934c4e50c feat(new-tool): keycode info 2023-02-06 23:13:20 +01:00
Corentin Thomasset
4a5734d4a3 chore(release): 2.18.0 2023-02-04 09:59:56 +01:00
Tsonglew
f708f5091e feat(new-tool): json minify (#265)
Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2023-02-04 09:56:17 +01:00
Corentin Thomasset
db817a2459 refactor(tools): config in query params 2023-01-28 21:06:16 +01:00
Corentin Thomasset
119041c185 chore(release): 2.17.0 2023-01-13 18:27:39 +01:00
Corentin Thomasset
4607837f9a feat(new-tool): temperature converter 2023-01-13 18:26:28 +01:00
Corentin Thomasset
f52f7a845c refactor(jwt-parser): simplified code 2023-01-13 14:02:44 +01:00
bastantoine
acc7f0a586 feat(new-tool): jwt parser (#262)
* npm install jwt-decode

* added base tool structure

* added function to decode JWT and display header and payload

* use a table to display the data

* show human readable values

* added switch to toggle display of parsed values

* lint

* replaced basic package-lock.json with pnpm-lock.json

* change the icon of the tool

* simplify return

* use camelCase

* added description of the tool

* always parse the values

* use camelCase...
2022-12-27 09:38:35 +01:00
Corentin Thomasset
ebb7301a98 chore(release): 2.16.0 2022-12-21 21:15:16 +01:00
Corentin Thomasset
def60e7248 refactor(tracker): better tracker injection 2022-12-21 21:02:57 +01:00
Corentin Thomasset
bf88836dbe feat(search-bar): use cmd + k to focus on mac 2022-12-21 00:21:12 +01:00
Corentin Thomasset
bfc2e24bbf feat(tracker): added actions monitoring 2022-12-21 00:03:31 +01:00
Corentin Thomasset
40872859a5 refactor(clean): removed unused import 2022-12-20 21:15:00 +01:00
Corentin Thomasset
cf723f144e refactor(clean): removed empty style tag 2022-12-20 21:14:40 +01:00
Corentin Thomasset
7f964941d3 chore(docs): updated readme 2022-12-20 20:57:24 +01:00
Corentin Thomasset
af075dcccc feat(tool): improved favorite tool management 2022-12-17 15:33:52 +01:00
Corentin Thomasset
274ff02b54 chore(git): added .env to gitignore 2022-12-17 11:42:47 +01:00
Corentin Thomasset
679dd1c1f6 refactor(menu): improve support button 2022-12-17 01:30:27 +01:00
Corentin Thomasset
4cd809bd0c feat(tools): added favorite tool handling 2022-12-17 01:30:02 +01:00
Corentin Thomasset
8d09086e78 chore(release): 2.15.0 2022-12-16 21:59:47 +01:00
Corentin Thomasset
acf8bc11db fix(tool-card): correct text color on light mode for card description 2022-12-16 21:57:23 +01:00
Corentin Thomasset
71e98e93e5 feat(search-bar): better search back result 2022-12-16 21:44:54 +01:00
Corentin Thomasset
1b5d4e72bd refactor(search-bar): improved tool fuzzy search 2022-12-16 18:10:50 +01:00
Corentin Thomasset
8476cf319b fix(integer-base-converter): handle non-decimal char and better error message 2022-12-07 21:52:24 +01:00
Corentin Thomasset
0ff853437b chore(release): 2.14.1 2022-11-23 22:00:08 +01:00
Corentin Thomasset
39c8f92065 chore(release): 2.14.0 2022-11-23 22:00:01 +01:00
Corentin Thomasset
35b5187119 feat(new-tool): chmod calculator 2022-11-23 21:57:38 +01:00
Corentin Thomasset
94698cea50 chore(release): 2.13.0 2022-11-14 22:17:34 +01:00
Corentin Thomasset
8294cd68da chore(deps): upgraded dependencies 2022-11-14 22:13:37 +01:00
Corentin Thomasset
7c9b8ac178 feat(new-tool): mime type to extension converter 2022-11-14 22:13:26 +01:00
Corentin Thomasset
5d8f46abf8 refactor(ui): change sponsor button location and caption 2022-11-13 23:16:57 +01:00
Corentin Thomasset
35a3760771 fix(ui): remove icon transparency overlap 2022-11-13 23:13:50 +01:00
Corentin Thomasset
4ef25887b9 feat(date-converter): added mongodb objectID format 2022-09-09 11:53:06 +02:00
Corentin Thomasset
7f229959d6 refactor(otp-generator): changed url 2022-08-24 00:22:54 +02:00
Corentin Thomasset
d3a2936979 chore(release): 2.12.0 2022-08-24 00:18:01 +02:00
Corentin Thomasset
5f16885923 feat(new-tool): added otp generator 2022-08-24 00:18:01 +02:00
Corentin Thomasset
ea5e7a7fc7 feat(config): added tsx to allowed extension 2022-08-24 00:18:01 +02:00
Corentin Thomasset
7de6c86f9e refactor(useQRCode): switched args to MaybeRef 2022-08-24 00:18:01 +02:00
Corentin Thomasset
83da6b7ee9 refactor: token generator can use a custom alphabet 2022-08-24 00:18:01 +02:00
Corentin Thomasset
737319edf1 chore(release): 2.11.0 2022-08-19 17:47:34 +02:00
Corentin Thomasset
a77a82f5a2 chore: updated ci workflow to handle pnpm 2022-08-19 17:42:46 +02:00
Corentin Thomasset
da17696293 refactor(colored-card): added transition on like hover 2022-08-19 17:40:13 +02:00
Corentin Thomasset
164e32b442 feat(new-tool): meta tag generator 2022-08-19 17:40:00 +02:00
Corentin Thomasset
49755909bd fix(deps): added missing optional deps 2022-08-18 10:53:23 +02:00
Corentin Thomasset
44d653b1f2 chore(deps): switched to pnpm 2022-08-18 10:35:22 +02:00
Corentin Thomasset
7c449f4f2d fix: removed colored card border 2022-08-17 17:33:44 +02:00
Corentin Thomasset
ab7483b5c2 feat: added colored share card 2022-08-17 17:06:48 +02:00
Corentin THOMASSET
5222bd5d04 refactor(share): updated share meta 2022-08-16 15:22:41 +02:00
Corentin Thomasset
cf5e4d9056 chore(release): 2.10.3 2022-08-14 10:55:06 +02:00
Corentin Thomasset
992f96b48a refactor(share): updated twitter meta tags 2022-08-14 10:53:45 +02:00
Corentin Thomasset
fcf4cfe64d refactor(share): new share banner 2022-08-14 10:53:39 +02:00
Corentin Thomasset
f54223fb0a refactor(validation): simplified validation management with helpers 2022-08-04 21:59:48 +02:00
Corentin Thomasset
b38ab82d05 chore(release): 2.10.2 2022-08-04 23:29:47 +02:00
Corentin Thomasset
f6cd9b76d3 refactor(dry): mutualised duplicated code with withDefaultOnError 2022-08-04 23:14:32 +02:00
Corentin Thomasset
208a373fd0 refactor(lint): added import rules 2022-08-04 22:46:50 +02:00
Corentin Thomasset
8089c60000 refactor(json-prettifier): more permissive json parser 2022-08-04 22:18:15 +02:00
Corentin Thomasset
d30cd8a9ab refactor(home): removed new tool first sort 2022-08-04 21:59:22 +02:00
Corentin Thomasset
04a8e122be chore(release): 2.10.1 2022-08-04 12:16:54 +02:00
Corentin Thomasset
447bdf2148 refactor(base64): mutualized base64 functions into global utilities 2022-08-04 12:09:32 +02:00
Corentin Thomasset
ca7cb44389 fix(bip39-generator): cleared an issue with the mnemonic validation 2022-08-04 12:08:23 +02:00
Corentin Thomasset
e48d60b1ed refactor(chronometer): improved chronometer precision 2022-08-04 09:06:42 +02:00
Corentin Thomasset
fda0b0ca25 fix(import): removed auto added weird .js extension 2022-08-04 08:50:15 +02:00
Corentin Thomasset
cc717bc87e chore(release): 2.10.0 2022-08-03 17:25:48 +02:00
Corentin Thomasset
1bc6380c6f feat(new-tool): hmac generator 2022-08-03 17:19:53 +02:00
Corentin Thomasset
02c4963531 refactor(lint): externalization of prettier for simpler IDE support 2022-08-03 17:19:53 +02:00
Corentin Thomasset
129f74c371 feat(new-tool): an svg placeholder image generator 2022-08-03 17:18:18 +02:00
Corentin Thomasset
0be33fb337 refactor(display): mutualized code display 2022-08-03 17:18:18 +02:00
Corentin THOMASSET
422b6eb05a feat(hash-text): digest base selector (#254) 2022-08-03 17:16:03 +02:00
Corentin Thomasset
fad4833ca2 chore(deps): updated dependencies 2022-08-03 17:13:34 +02:00
marvin-j97
531a25c1c4 fix(eta-calculator): clamp inputs (#249) 2022-08-03 17:13:33 +02:00
Corentin Thomasset
77b5b0cab5 refactor(validation): simplified validation system 2022-08-03 17:13:33 +02:00
Corentin Thomasset
7570ad9656 fix(bip39-generator): typo in validation message 2022-08-03 17:13:33 +02:00
Corentin Thomasset
8a9e7888de fix(base64-to-string): prevent validation error 2022-08-03 17:13:33 +02:00
Corentin Thomasset
750a76b00f refactor(base64-to-file): clean validation to convert base64 to file 2022-08-03 17:13:33 +02:00
marvin-j97
5f03619ab4 fix(wording): removed spaces before ponctuation (#252) 2022-08-03 08:46:59 +02:00
Corentin Thomasset
352365f012 chore(release): 2.9.2 2022-07-28 19:31:16 +02:00
Corentin Thomasset
4f599b6999 fix(device-information): handle of unknown values 2022-07-28 19:30:36 +02:00
JWB
138149e6f0 fix(device-information): prevent unwanted y-truncature of text
* Device information page styling fix

I was checking out your site, and really like it! already added to my bookmarks, and when I was on the Device Information page I noticed the `p` and `y` in the words 1 dppx and landscape-primary were cut off and my ocd kicked in to tweak it.
The fix was to remove the line height set on the value, and then to keep the spacing how it was just moving it up 5px seemed to do the trick.

I'm using Firefox 102.0.1 on Windows 11. I'll attach some pics so you can see the before and after.

I've never messed with vue, but after looking at this file here ya make me want to check it out more. I love how its keeping it all together in one file.
Might also need to rebuild it the site after this change, but you get the point. 

Awesome site!

* Update src/tools/device-information/device-information.vue

Co-authored-by: Corentin THOMASSET <corentin.thomasset74@gmail.com>
2022-07-28 19:28:23 +02:00
Corentin Thomasset
412de23796 fix(base64-file): fixed url slug 2022-07-25 23:31:06 +02:00
Corentin Thomasset
1a22d55b3c refactor(base64-file): fixed typo 2022-07-25 23:23:53 +02:00
351 changed files with 24627 additions and 22222 deletions

291
.eslintrc-auto-import.json Normal file
View File

@@ -0,0 +1,291 @@
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true,
"InjectionKey": true,
"PropType": true,
"Ref": true,
"VNode": true,
"asyncComputed": true,
"autoResetRef": true,
"computed": true,
"computedAsync": true,
"computedEager": true,
"computedInject": true,
"computedWithControl": true,
"controlledComputed": true,
"controlledRef": true,
"createApp": true,
"createEventHook": true,
"createGlobalState": true,
"createInjectionState": true,
"createReactiveFn": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
"createUnrefFn": true,
"customRef": true,
"debouncedRef": true,
"debouncedWatch": true,
"defineAsyncComponent": true,
"defineComponent": true,
"eagerComputed": true,
"effectScope": true,
"extendRef": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"ignorableWatch": true,
"inject": true,
"isDefined": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"makeDestructurable": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onClickOutside": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onKeyStroke": true,
"onLongPress": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onStartTyping": true,
"onUnmounted": true,
"onUpdated": true,
"pausableWatch": true,
"provide": true,
"reactify": true,
"reactifyObject": true,
"reactive": true,
"reactiveComputed": true,
"reactiveOmit": true,
"reactivePick": true,
"readonly": true,
"ref": true,
"refAutoReset": true,
"refDebounced": true,
"refDefault": true,
"refThrottled": true,
"refWithControl": true,
"resolveComponent": true,
"resolveRef": true,
"resolveUnref": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"syncRef": true,
"syncRefs": true,
"templateRef": true,
"throttledRef": true,
"throttledWatch": true,
"toRaw": true,
"toReactive": true,
"toRef": true,
"toRefs": true,
"triggerRef": true,
"tryOnBeforeMount": true,
"tryOnBeforeUnmount": true,
"tryOnMounted": true,
"tryOnScopeDispose": true,
"tryOnUnmounted": true,
"unref": true,
"unrefElement": true,
"until": true,
"useActiveElement": true,
"useAnimate": true,
"useArrayDifference": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayFindLast": true,
"useArrayIncludes": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
"useArraySome": true,
"useArrayUnique": true,
"useAsyncQueue": true,
"useAsyncState": true,
"useAttrs": true,
"useBase64": true,
"useBattery": true,
"useBluetooth": true,
"useBreakpoints": true,
"useBroadcastChannel": true,
"useBrowserLocation": true,
"useCached": true,
"useClipboard": true,
"useCloned": true,
"useColorMode": true,
"useConfirmDialog": true,
"useCounter": true,
"useCssModule": true,
"useCssVar": true,
"useCssVars": true,
"useCurrentElement": true,
"useCycleList": true,
"useDark": true,
"useDateFormat": true,
"useDebounce": true,
"useDebounceFn": true,
"useDebouncedRefHistory": true,
"useDeviceMotion": true,
"useDeviceOrientation": true,
"useDevicePixelRatio": true,
"useDevicesList": true,
"useDialog": true,
"useDisplayMedia": true,
"useDocumentVisibility": true,
"useDraggable": true,
"useDropZone": true,
"useElementBounding": true,
"useElementByPoint": true,
"useElementHover": true,
"useElementSize": true,
"useElementVisibility": true,
"useEventBus": true,
"useEventListener": true,
"useEventSource": true,
"useEyeDropper": true,
"useFavicon": true,
"useFetch": true,
"useFileDialog": true,
"useFileSystemAccess": true,
"useFocus": true,
"useFocusWithin": true,
"useFps": true,
"useFullscreen": true,
"useGamepad": true,
"useGeolocation": true,
"useI18n": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,
"useIntersectionObserver": true,
"useInterval": true,
"useIntervalFn": true,
"useKeyModifier": true,
"useLastChanged": true,
"useLink": true,
"useLoadingBar": true,
"useLocalStorage": true,
"useMagicKeys": true,
"useManualRefHistory": true,
"useMediaControls": true,
"useMediaQuery": true,
"useMemoize": true,
"useMemory": true,
"useMessage": true,
"useMounted": true,
"useMouse": true,
"useMouseInElement": true,
"useMousePressed": true,
"useMutationObserver": true,
"useNavigatorLanguage": true,
"useNetwork": true,
"useNotification": true,
"useNow": true,
"useObjectUrl": true,
"useOffsetPagination": true,
"useOnline": true,
"usePageLeave": true,
"useParallax": true,
"useParentElement": true,
"usePerformanceObserver": true,
"usePermission": true,
"usePointer": true,
"usePointerLock": true,
"usePointerSwipe": true,
"usePreferredColorScheme": true,
"usePreferredContrast": true,
"usePreferredDark": true,
"usePreferredLanguages": true,
"usePreferredReducedMotion": true,
"usePrevious": true,
"useRafFn": true,
"useRefHistory": true,
"useResizeObserver": true,
"useRoute": true,
"useRouter": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
"useScriptTag": true,
"useScroll": true,
"useScrollLock": true,
"useSessionStorage": true,
"useShare": true,
"useSlots": true,
"useSorted": true,
"useSpeechRecognition": true,
"useSpeechSynthesis": true,
"useStepper": true,
"useStorage": true,
"useStorageAsync": true,
"useStyleTag": true,
"useSupported": true,
"useSwipe": true,
"useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true,
"useTextareaAutosize": true,
"useThrottle": true,
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
"useTimestamp": true,
"useTitle": true,
"useToNumber": true,
"useToString": true,
"useToggle": true,
"useTransition": true,
"useUrlSearchParams": true,
"useUserMedia": true,
"useVModel": true,
"useVModels": true,
"useVibrate": true,
"useVirtualList": true,
"useWakeLock": true,
"useWebNotification": true,
"useWebSocket": true,
"useWebWorker": true,
"useWebWorkerFn": true,
"useWindowFocus": true,
"useWindowScroll": true,
"useWindowSize": true,
"watch": true,
"watchArray": true,
"watchAtMost": true,
"watchDebounced": true,
"watchDeep": true,
"watchEffect": true,
"watchIgnorable": true,
"watchImmediate": true,
"watchOnce": true,
"watchPausable": true,
"watchPostEffect": true,
"watchSyncEffect": true,
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true,
"toValue": true
}
}

View File

@@ -1,30 +1,21 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution');
/**
* @type {import('eslint').Linter.Config}
*/
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-recommended',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier',
],
env: {
'vue/setup-compiler-macros': true,
},
extends: ['@antfu', './.eslintrc-auto-import.json', '@unocss'],
rules: {
'vue/multi-word-component-names': ['off'],
'prettier/prettier': [
'error',
{
singleQuote: true,
semi: true,
tabWidth: 2,
trailingComma: 'all',
printWidth: 120,
},
],
'curly': ['error', 'all'],
'@typescript-eslint/semi': ['error', 'always'],
'@typescript-eslint/no-use-before-define': ['error', { allowNamedExports: true, functions: false }],
'vue/no-empty-component-block': ['error'],
'no-restricted-imports': ['error', {
paths: [{
name: '@vueuse/core',
importNames: ['useClipboard'],
message: 'Please use local useCopy from src/composable/copy.ts instead of useClipboard.',
}],
}],
},
};

View File

@@ -1,17 +1,17 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] "
about: Create a report to help us improve our tools
title: '[BUG] '
labels: bug
assignees: CorentinTh
---
**Describe the bug**
A clear and concise description of what the bug is.
**Which tool is impacted?**
Example: the token generator
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -24,10 +24,11 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Configuration (please complete the following information):**
- Device: [e.g. iPhone6, ]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- Device: [e.g. iPhone6, ]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[FEAT]"
labels: enhancement
assignees: CorentinTh
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,19 @@
---
name: New tool request
about: Suggest a new tool idea
title: '[NEW TOOL]'
labels: new tool
assignees: CorentinTh
---
**What tool do you want?**
Example: a token generator
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Is their example of this tool in the wild?**
Provide link to already existing tool or npm packages if any exists
**Additional context**
Add any other context about the feature request here.

13
.github/ISSUE_TEMPLATE/other-request.md vendored Normal file
View File

@@ -0,0 +1,13 @@
---
name: Other request
about: Any request that does not concern a tool creation, a new feature request on a tool or a bug
title: '[OTHER] '
labels:
assignees: CorentinTh
---
**Describe the solution you'd like**
A clear and concise description of what you want.
**Additional context**
Add any other context about the feature request here.

View File

@@ -0,0 +1,13 @@
---
name: Tool improvement
about: Improvement on an existing tool
title: '[TOOL IMPROVEMENT]'
labels: enhancement
assignees: CorentinTh
---
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context about the feature request here.

View File

@@ -1,38 +1,36 @@
name: ci
on: [push, pull_request]
on:
pull_request:
push:
branches:
- main
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 0
- name: Setup node env
uses: actions/setup-node@v3.0.0
- uses: actions/checkout@v3
- run: corepack enable
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Cache node_modules
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
cache: 'pnpm'
- name: Install dependencies
run: npm ci
run: pnpm i
- name: Run linters
run: npm run lint
run: pnpm lint
- name: Run unit test
run: npm run test
run: pnpm test
- name: Type check
run: pnpm typecheck
- name: Build the app
run: npm run build
env:
NODE_OPTIONS: --max-old-space-size=4096
run: pnpm build

View File

@@ -37,11 +37,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -52,7 +52,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -66,4 +66,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -0,0 +1,87 @@
name: Docker nightly release
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
jobs:
check_date:
runs-on: ubuntu-latest
name: Check latest commit
outputs:
should_run: ${{ steps.should_run.outputs.should_run }}
steps:
- uses: actions/checkout@v3
- name: print latest_commit
run: echo ${{ github.sha }}
- id: should_run
continue-on-error: true
name: check latest commit is less than a day
if: ${{ github.event_name == 'schedule' }}
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
ci:
runs-on: ubuntu-latest
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
steps:
- uses: actions/checkout@v3
- run: corepack enable
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'pnpm'
- name: Install dependencies
run: pnpm i
- name: Run linters
run: pnpm lint
- name: Run unit test
run: pnpm test
- name: Build the app
run: pnpm build
build:
runs-on: ubuntu-latest
needs:
- ci
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
corentinth/it-tools:nightly
ghcr.io/corentinth/it-tools:nightly

44
.github/workflows/e2e-tests.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: E2E tests
on: [deployment_status]
jobs:
test:
if: github.event.deployment_status.state == 'success'
timeout-minutes: 60
runs-on: ubuntu-latest
env:
BASE_URL: ${{ github.event.deployment_status.target_url }}
strategy:
matrix:
shard: [1/3, 2/3, 3/3]
steps:
- uses: actions/checkout@v3
- run: corepack enable
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'pnpm'
- name: Get Playwright version
id: playwright-version
run: echo "PLAYWRIGHT_VERSION=$(jq -r .dependencies.playwright package.json)" >> "$GITHUB_OUTPUT"
- name: Install dependencies
run: pnpm install
- name: Restore Playwright browsers from cache
uses: actions/cache@v3
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}-${{ hashFiles('**/playwright.config.ts') }}
restore-keys: |
${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}-
${{ runner.os }}-playwright-
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm run test:e2e --shard=${{ matrix.shard }}

104
.github/workflows/releases.yml vendored Normal file
View File

@@ -0,0 +1,104 @@
name: Release new versions
on:
push:
tags:
- 'v*.*.*'
jobs:
docker-release:
runs-on: ubuntu-latest
steps:
- name: Get release version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: |
corentinth/it-tools:latest
corentinth/it-tools:${{ env.RELEASE_VERSION }}
ghcr.io/corentinth/it-tools:latest
ghcr.io/corentinth/it-tools:${{ env.RELEASE_VERSION}}
github-release:
runs-on: ubuntu-latest
needs: docker-release
steps:
- name: Get release version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@v3
- run: corepack enable
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'pnpm'
- name: Install dependencies
run: pnpm i
- name: Build the app
run: pnpm build
- name: Zip the app
run: zip -r it-tools-${{ env.RELEASE_VERSION }}.zip dist/*
- name: Get changelog
id: changelog
run: |
EOF=$(openssl rand -hex 8)
echo "changelog<<$EOF" >> $GITHUB_OUTPUT
node ./scripts/getLatestChangelog.mjs >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT
- name: Create Release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
files: it-tools-${{ env.RELEASE_VERSION }}.zip
tag_name: v${{ env.RELEASE_VERSION }}
draft: true
prerelease: false
body: |
## Docker images
- Docker Hub
- `corentinth/it-tools:latest`
- `corentinth/it-tools:${{ env.RELEASE_VERSION }}`
- GitHub Container Registry
- `ghcr.io/corentinth/it-tools:latest`
- `ghcr.io/corentinth/it-tools:${{ env.RELEASE_VERSION}}`
## Changelog
${{ steps.changelog.outputs.changelog }}

7
.gitignore vendored
View File

@@ -26,3 +26,10 @@ coverage
*.njsproj
*.sln
*.sw?
.env
/test-results/
/playwright-report/
/playwright/.cache/
# Webkit with playwright creates a salt file
salt

2
.nvmrc
View File

@@ -1 +1 @@
16.14.2
18.17.1

7
.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"singleQuote": true,
"semi": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 120
}

View File

@@ -1,3 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin", "dbaeumer.vscode-eslint"]
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin", "dbaeumer.vscode-eslint", "lokalise.i18n-ally"]
}

View File

@@ -2,361 +2,211 @@
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.9.1](https://github.com/CorentinTh/it-tools/compare/v2.9.0...v2.9.1) (2022-07-25)
### Refactors
* **base64:** split base64 text and file conversion in two tools + base64 to file ([e6953d1](https://github.com/CorentinTh/it-tools/commit/e6953d1b67b81a6d3c19973b706f29637c421f98))
## [2.9.0](https://github.com/CorentinTh/it-tools/compare/v2.8.0...v2.9.0) (2022-07-25)
## Version 2023.08.21-6f93cba
### Features
- **copy**: support legacy copy to clipboard for older browser (#581) (6f93cba)
- **new tool**: string obfuscator (#575) (c58d6e3)
* **new-tool:** added a basic auth generator ([bdee93a](https://github.com/CorentinTh/it-tools/commit/bdee93a9e45c6b46e7f75cdcbe1907f138722dca))
### Bug fixes
- **deps**: update dependency sql-formatter to v12 (#520) (2bcb77a)
## [2.8.0](https://github.com/CorentinTh/it-tools/compare/v2.7.0...v2.8.0) (2022-07-24)
### Chores
- **deps**: switched to fucking typescript v5 (#501) (76b2761)
- **deps**: update dependency @antfu/eslint-config to ^0.40.0 (#552) (6ff9a01)
- **deps**: update dependency prettier to v3 (#564) (a2b9b15)
- **deps**: removed @typescript-eslint/parser (#563) (144f86e)
- **deps**: removed ts-pattern (#565) (0f1f659)
## Version 2023.08.16-9bd4ad4
### Features
- **Case Converter**: Add lowercase and uppercase (#534) (7b6232a)
- **new tool**: emoji picker (#551) (93f7cf0)
- **ui**: added c-select in the ui lib (#550) (dfa1ba8)
- **new-tool**: password strength analyzer (#502) (a9c7b89)
- **new-tool**: yaml to toml (e29b258)
- **new-tool**: json to toml (ea50a3f)
- **new-tool**: toml to yaml (746e5bd)
- **new-tool**: toml to json (c7d4f11)
- **command-palette**: random tool action (ec4c533)
- **config**: allow app to run in a subfolder via BASE_URL (#461) (6304595)
- **new-tool**: percentage calculator (#456) (b9406a4)
- **new-tool**: json to csv converter (69f0bd0)
- **new tool**: xml formatter (#457) (a6bbeae)
- **chmod-calculator**: added symbolic representation (#455) (f771e7a)
- **enhancement**: use system dark mode (#458) (cf7b1f0)
- **phone-parser**: searchable country code select (d2956b6)
- **new tool**: camera screenshot and recorder (34d8e5c)
- **base64-string-converter**: switch to encode and decode url safe base64 strings (#392) (0b20f1c)
* **new-tool:** added an ETA calculator ([125a502](https://github.com/CorentinTh/it-tools/commit/125a50215a7abb9e0b59dbbc62aee49007b05ffe))
### Bug fixes
- **deps**: update dependency uuid to v9 (#566) (5e12991)
- **deps**: update dependency mathjs to v11 (#519) (7924456)
- **deps**: update dependency @vueuse/router to v10 (#516) (ea0f27c)
- **copy**: prevent shorthand copy if source is present in useCopy (#559) (86e964a)
- **c-lib**: hide component library shortcut link in non-dev (#557) (56d74d0)
- **emoji picker**: fix copy button (#556) (e5d0ba7)
- **deps**: update dependency @vueuse/head to v1 (#515) (d12dd40)
- **deps**: update dependency country-code-lookup to ^0.1.0 (#493) (8c72e69)
- **deps**: update dependency @vueuse/head to ^0.9.0 (#492) (cec9dea)
- **i18n**: fallback for demo i18n (12d9e5d)
- **typos**: fixed more typos & uppercase JSON (#475) (9526ed8)
- **about**: typos and wording (#474) (7068610)
- **mime-types**: typos (#470) (c4cec9e)
- **sonar**: took down minor sonar warning (4cbd7ac)
- **readme**: typo (105b21b)
- **ipv4-range-expander**: calculate correct for ip addresses where the first octet is lower than 128 (#405) (8c92d56)
- **ipv4-converter**: removed readonly on input (7aed9c5)
### Refactoring
- **navbar**: consistent spacing in navbar buttons (#507) (30f88fc)
- **ui**: remove n-text (#506) (72c98a3)
- **ui**: replaced some n-input to c-input (#505) (05ea545)
- **json-viewer**: input monospace font (#485) (9125dcf)
- **search**: command palette design (#463) (bcb98b3)
- **c-input-text**: force usage of props with default (1e2a35b)
- **naming**: prevent auto import conflicts for git memo (45c2474)
- **imports**: removed unnecessary imports to vue (fe61f0f)
- **ui**: removed all n-space (4d2b037)
- **ui**: replaced some n-input with c-input-text (f7fc779)
### Bug Fixes
* **sql-prettifier:** better responsiveness ([560fcf3](https://github.com/CorentinTh/it-tools/commit/560fcf3f783c66b9197e4a015420c43a729518bc))
### Refactors
* **json-prettify:** improved layout for the json prettifier ([328fda6](https://github.com/CorentinTh/it-tools/commit/328fda65b3490869328467c5e2d5f538c689d9b6))
* **sql-prettifier:** remove unused service files ([ba87097](https://github.com/CorentinTh/it-tools/commit/ba87097e3d834b6ea3212d28c2c33badb95f85e1))
## [2.7.0](https://github.com/CorentinTh/it-tools/compare/v2.6.0...v2.7.0) (2022-07-24)
### Features
* **new-tool:** added an SQL prettifier and formatter ([d1f95f5](https://github.com/CorentinTh/it-tools/commit/d1f95f5b34a4570f1033a5289f0bd009d1aefb0c))
### Bug Fixes
* **typo:** fix few typos ([6cd25a7](https://github.com/CorentinTh/it-tools/commit/6cd25a743e32fceeaec8c1f8b94927a9c5d901f1))
## [2.6.0](https://github.com/CorentinTh/it-tools/compare/v2.5.3...v2.6.0) (2022-07-23)
### Features
* **new-tool:** added chronometer ([130031c](https://github.com/CorentinTh/it-tools/commit/130031c2256f3d4d46948974b9de85ee6e92bf8b))
* **search:** focus the search bar using Ctrl+K ([ab53048](https://github.com/CorentinTh/it-tools/commit/ab53048d5f6fdca7d00edbb79dee1a5409e6b11e))
### Bug Fixes
* **deps:** run dependencie audit auto fix ([a16161c](https://github.com/CorentinTh/it-tools/commit/a16161cdb48c064882b9dc91ec3d091d286f5c63))
* **lint:** cleanned index.html ([c3a302b](https://github.com/CorentinTh/it-tools/commit/c3a302bc389a0e13aef4b14d5a9d3ec3a0d32729))
* **text-statistics:** empty text mean 0 words and 0 lines ([92ce419](https://github.com/CorentinTh/it-tools/commit/92ce419f45e110509ab202485a36bf175ce345da))
### Refactors
* added accessibility labels on icon buttons ([394d085](https://github.com/CorentinTh/it-tools/commit/394d085846d976219ea775c21cd7e77f0f72a12b))
* **import:** auto reordered imports ([2140842](https://github.com/CorentinTh/it-tools/commit/214084262cec7fb881fd397626356b080ea1a5cc))
### [2.5.3](https://github.com/CorentinTh/it-tools/compare/v2.5.2...v2.5.3) (2022-07-21)
### Bug Fixes
* updated license in README ([e371e8f](https://github.com/CorentinTh/it-tools/commit/e371e8fedfd68f3cf6ecd3fbc9e2da8849f7d5bd))
### [2.5.2](https://github.com/CorentinTh/it-tools/compare/v2.5.1...v2.5.2) (2022-07-21)
### [2.5.1](https://github.com/CorentinTh/it-tools/compare/v2.5.0...v2.5.1) (2022-06-01)
### Bug Fixes
* **lint:** missing dangling comma ([f05c8e1](https://github.com/CorentinTh/it-tools/commit/f05c8e1dc69275e529f4c8771ad55ba211e7fb5e))
* menu label key value was undefined ([f48cd05](https://github.com/CorentinTh/it-tools/commit/f48cd058cf3381f3bc92ea8fe37b565327707d1e))
* **title:** trully reactive tool title ([c2e1d59](https://github.com/CorentinTh/it-tools/commit/c2e1d59cb9d8dbb1bb072a46100192cb8c59f59b))
* tool sorting inconsistencies in home page ([5ab4dd3](https://github.com/CorentinTh/it-tools/commit/5ab4dd3d4a42c3609d72597c7ba91764170e6e96))
## [2.5.0](https://github.com/CorentinTh/it-tools/compare/v2.4.2...v2.5.0) (2022-06-01)
### Features
* **new-tool:** math evaluator ([433ba2a](https://github.com/CorentinTh/it-tools/commit/433ba2a3e5419eed0c96304b37693082224a1c73))
* **tools:** new badge for recently created tools ([11720e6](https://github.com/CorentinTh/it-tools/commit/11720e6cdefc1da4bdd638415813b609840f8462))
### Bug Fixes
* **config:** updated env values loading ([2f61c74](https://github.com/CorentinTh/it-tools/commit/2f61c745f57962cf3bb9e2c1db4a3176df042808))
### Refactors
* removed unused import ([8fb0e6a](https://github.com/CorentinTh/it-tools/commit/8fb0e6af9c3be708d3f1777a1661e1b38f197a3f))
* renammed Tool.ts to tool.ts ([ac89490](https://github.com/CorentinTh/it-tools/commit/ac89490794ee3c1c033859ffea31a962a13cc96d))
### [2.4.2](https://github.com/CorentinTh/it-tools/compare/v2.4.1...v2.4.2) (2022-06-01)
### Refactors
* **config:** added config management with figue ([6becdbb](https://github.com/CorentinTh/it-tools/commit/6becdbb42329e1bdecf158707e37ba9f13ba1d2c))
* **imports:** removed useless defineProps import ([5ce1262](https://github.com/CorentinTh/it-tools/commit/5ce1262fb44864b829dac09d5c0b9b68d522ceb7))
* set coerent head title for home page ([a46d125](https://github.com/CorentinTh/it-tools/commit/a46d125c19902c2f41f37c62c07bb7b548d9f6f0))
### [2.4.1](https://github.com/CorentinTh/it-tools/compare/v2.4.0...v2.4.1) (2022-05-15)
### Bug Fixes
* **seo:** wrong url in share metas ([a88e4a9](https://github.com/CorentinTh/it-tools/commit/a88e4a9289e7d8cc80190f60f2fe08fe2ba08ee6))
### Refactors
* **json-viewer:** add clear button ([048bc4a](https://github.com/CorentinTh/it-tools/commit/048bc4ae943509dea2946764efaa69f845b6c478))
* **seo:** changed title string ([d4ea393](https://github.com/CorentinTh/it-tools/commit/d4ea393c1df87ae958a06ed66a11e36b081282d4))
## [2.4.0](https://github.com/CorentinTh/it-tools/compare/v2.3.2...v2.4.0) (2022-05-14)
### Features
* catch throw on validation ([a60f64f](https://github.com/CorentinTh/it-tools/commit/a60f64f74417f811204121f97c16cdb4754afc3b))
* **hash-text:** compute all hashes at the same time ([#242](https://github.com/CorentinTh/it-tools/issues/242)) ([e9cc499](https://github.com/CorentinTh/it-tools/commit/e9cc499ed87ba926086323223c7eca4f6658b3f0))
* **new-tool:** json viewer ([d356b14](https://github.com/CorentinTh/it-tools/commit/d356b1488fc640a4f5b65d62e0f2f368f5941996))
* **seo:** added cannonical meta ([34bc6a5](https://github.com/CorentinTh/it-tools/commit/34bc6a57a7bab98ff2a630d02034c342084e0af9))
### Bug Fixes
* **lint:** missing new lines ([3cfc5f8](https://github.com/CorentinTh/it-tools/commit/3cfc5f8bc27b66e6fbb6054f3c909818083ebc37))
* update recommended extension ids ([#244](https://github.com/CorentinTh/it-tools/issues/244)) ([1d7032d](https://github.com/CorentinTh/it-tools/commit/1d7032d0268220f594de6d837a303fc1e63cbd9f))
### Chores
- **deps**: update dependency vitest to ^0.34.0 (#562) (9bd4ad4)
- **deps**: update dependency node to v18.17.1 (#560) (65a9474)
- **deps**: update dependency unocss to ^0.55.0 (#561) (85cc7a8)
- **deps**: update dependency @unocss/eslint-config to ^0.55.0 (#553) (4268e25)
- **deps**: update dependency @intlify/unplugin-vue-i18n to ^0.12.0 (#526) (d1c8880)
- **deps**: update docker/login-action action to v2 (#512) (99bc84c)
- **deps**: update dependency jsdom to v22 (#499) (cd5a503)
- **deps**: update dependency @vitejs/plugin-vue-jsx to v3 (#497) (1a60236)
- **deps**: update dependency @vitejs/plugin-vue to v4 (#496) (a249421)
- **deps**: update dependency vite-plugin-pwa to ^0.16.0 (#488) (6498c9b)
- **deps**: update dependency vite to v4 (#503) (f40d7ec)
- **ci**: e2e against vercel deployement (#518) (2e28c50)
- **e2e**: execute e2e against built app (#511) (cf382b5)
- **deps**: update github/codeql-action action to v2 (#513) (0152583)
- **deps**: update node.js to v18 (#514) (38cb61d)
- **deps**: switched from vite-plugin-md to vite-plugin-vue-markdown (#510) (354aed6)
- **deps**: update dependency workbox-window to v7 (#509) (6b8682f)
- **deps**: update dependency vite-svg-loader to v4 (#508) (9e8349d)
- **deps**: update dependency typescript to ~4.9.0 (#481) (f440507)
- **deps**: update dependency vue-tsc to ^0.40.0 (#490) (b0d9a3e)
- **deps**: updated unplugin-auto-import (#504) (5c3bebf)
- **deps**: removed start-server-and-test dependency (8df7cd0)
- **deps**: update dependency c8 to v8 (#498) (6bda2ca)
- **deps**: update dependency @types/jsdom to v21 (#495) (994a1c3)
- **deps**: update node.js to v16.20.1 (#491) (05edaf4)
- **deps**: update dependency vitest to ^0.32.0 (#489) (49eacea)
- **deps**: update actions/checkout action to v3 (#494) (3f7d469)
- **deps**: update dependency unplugin-vue-components to ^0.25.0 (#484) (5f21908)
- **deps**: update dependency unplugin-auto-import to ^0.16.0 (#483) (6cb0845)
- **deps**: update dependency unocss to ^0.53.0 (#482) (38710dc)
- **deps**: update dependency @unocss/eslint-config to ^0.53.0 (#478) (282cfc4)
- **deps**: added renovate.json (#477) (363c2e4)
- **i18n**: tool scoped locales (#471) (1b038c7)
- **wysiwyg-editor**: update tiptap dependencies (732da08)
- **i18n**: setup i18n plugin config (ebfb872)
- **config**: netlify deployment support (#443) (93799af)
- **ci**: shard e2e tests (962a6d6)
- **lint**: switched to a better lint config (33c9b66)
### Refacor
- **transformers**: use monospace font for JSON and SQL text areas (#476) (ba4876d)
### Documentation
- **ide**: updated vscode extensions settings (#472) (847323c)
* added producthunt banners ([4c4da16](https://github.com/CorentinTh/it-tools/commit/4c4da16970e1dbb13705d8b6c020cd40cd2b5e0d))
### Refactors
* **base-layout:** renammed one letter variable ([383d975](https://github.com/CorentinTh/it-tools/commit/383d97569580c4f31448c07cb97e3778bc97a8af))
* **date-converter:** mutualised and dry-ed code ([d2c767f](https://github.com/CorentinTh/it-tools/commit/d2c767f0922e9b93172c3167226ad3db5499b9f6))
* **seo:** changed title string ([c3b6132](https://github.com/CorentinTh/it-tools/commit/c3b6132c261bd5952bafb1ff1e576eb13d2d0a7d))
* updated description ([b89db3c](https://github.com/CorentinTh/it-tools/commit/b89db3c8d0de601fecbd2f9f79492dff1b461bd8))
### [2.3.2](https://github.com/CorentinTh/it-tools/compare/v2.3.1...v2.3.2) (2022-05-09)
### Bug Fixes
* **base-converter:** responsive input ([0b0cbd5](https://github.com/CorentinTh/it-tools/commit/0b0cbd55c3809ded2eedfa0b2238bc950b01516a))
* **base64-converter:** async onUpload callback ([84cf1bb](https://github.com/CorentinTh/it-tools/commit/84cf1bb9645c5ae31579098df59471f7d99f6f0c))
* **typo:** misspelings ([9755e51](https://github.com/CorentinTh/it-tools/commit/9755e51fe216e5e25c56417152e70cb5bce26b11))
### Refactors
* **responsive:** row layout for multicards on big screens ([e21230b](https://github.com/CorentinTh/it-tools/commit/e21230bbd9550ba3315607b021a60a4f9f9e1b61))
### [2.3.1](https://github.com/CorentinTh/it-tools/compare/v2.3.0...v2.3.1) (2022-04-24)
### Refactors
* changed twitter account handler ([608ec3a](https://github.com/CorentinTh/it-tools/commit/608ec3a81db6583c8a2bf126b3868afd043c6981))
## [2.3.0](https://github.com/CorentinTh/it-tools/compare/v2.2.0...v2.3.0) (2022-04-22)
### Chors
- **deps**: updated vueuse dependency version (8515c24)
## Version 2023.05.14-77f2efc
### Features
- **list-converter**: a small converter who deals with column based data and do some stuff with it (#387) (83a7b3b)
- **new tool**: phone parser and normalizer (ce3150c)
* **new-tool:** html entities escape/unescape ([8e29a97](https://github.com/CorentinTh/it-tools/commit/8e29a97404ea0aa9b9b576656358c8c276b6f992))
### Bug fixes
- **phone-parser**: use default country code (a43c546)
- **home**: prevent weird blue border on card (3f6c8f0)
### Refactoring
- **ui**: replaced some n-input with c-input-text (77f2efc)
### Bug Fixes
### Chores
- **issues**: updated new tool request issue template (edae4c6)
* **head:** added titles for non-tool pages ([0a15892](https://github.com/CorentinTh/it-tools/commit/0a15892dde9852ff158a8fcb72d0ad6bae8bad02))
* **sider:** default collapsed value ([b22aa94](https://github.com/CorentinTh/it-tools/commit/b22aa941f52009118d4d3cc98277cc4c402a4c77))
* **sider:** missing href for link in footer ([c4dabcc](https://github.com/CorentinTh/it-tools/commit/c4dabccdaeac9d03163ac2588599b000e4e74562))
* **style:** hard width for group labels ([ebf6695](https://github.com/CorentinTh/it-tools/commit/ebf6695d2533db6f37b24dc7d338f422c551c8cb))
* **url-parser:** cleaned weird margins on dark mode ([005ebfb](https://github.com/CorentinTh/it-tools/commit/005ebfba318ece1a9c04aefb737baed5d7aafb91))
### Refactors
* **lint:** linter auto fix ([086d31e](https://github.com/CorentinTh/it-tools/commit/086d31eab5b3b1a927803eab5e650585f61abe19))
* removed useless ref and value ([b12cbe4](https://github.com/CorentinTh/it-tools/commit/b12cbe412407389186a58e4ceaa94f5b441c11ea))
### [2.2.1](https://github.com/CorentinTh/it-tools/compare/v2.2.0...v2.2.1) (2022-04-21)
### Bug Fixes
* **head:** added titles for non-tool pages ([0a15892](https://github.com/CorentinTh/it-tools/commit/0a15892dde9852ff158a8fcb72d0ad6bae8bad02))
* **sider:** missing href for link in footer ([c4dabcc](https://github.com/CorentinTh/it-tools/commit/c4dabccdaeac9d03163ac2588599b000e4e74562))
* **style:** hard width for group labels ([ebf6695](https://github.com/CorentinTh/it-tools/commit/ebf6695d2533db6f37b24dc7d338f422c551c8cb))
* **url-parser:** cleaned weird margins on dark mode ([005ebfb](https://github.com/CorentinTh/it-tools/commit/005ebfba318ece1a9c04aefb737baed5d7aafb91))
## [2.2.0](https://github.com/CorentinTh/it-tools/compare/v2.1.0...v2.2.0) (2022-04-18)
### Ui-lib
- **new-component**: added text input component in the c-lib (aad8d84)
- **button**: size variants (401f13f)
## Version 2023.04.23-92bd835
### Features
- **ui-lib**: demo pages for c-lib components (92bd835)
- **new-tool**: diff of two json objects (362f2fa)
- **ipv4-range-expander**: expands a given IPv4 start and end address to a valid IPv4 subnet (#366) (df989e2)
- **date converter**: auto focus main input (6d22025)
* **new-tool:** url parser ([2b38d6f](https://github.com/CorentinTh/it-tools/commit/2b38d6f81e34845f896b858513e35209cba29f98))
### Bug fixes
- **ts**: cleaned legacy typechecking warning (e88c1d5)
- **mac-address-lookup**: added copy handler on button click (c311e38)
### Refactoring
- **ui-lib**: prevent c-button to shrink (61ece23)
- **ui**: replaced naive ui cards with custom ones (f080933)
- **clean**: removed unused lodash import (bb32513)
- **clean**: removed useless br tags (74073f5)
- **ui**: getting ride of naive ui buttons (c45bce3)
### Bug Fixes
* **sider-footer:** fixed commit sha url ([ed9046d](https://github.com/CorentinTh/it-tools/commit/ed9046d3e1f5a7dc01c722ed139a2ae477a2d48f))
## [2.1.0](https://github.com/CorentinTh/it-tools/compare/v2.0.2...v2.1.0) (2022-04-18)
## Version 2023.04.14-dbad773
### Features
- **new-tool**: http status codes (8355bd2)
* **new-tool:** bcrypt ([6d5856f](https://github.com/CorentinTh/it-tools/commit/6d5856fa93d1ffbf71856c75adc24ad87dc4b49b))
* **new-tool:** device information ([277bd5f](https://github.com/CorentinTh/it-tools/commit/277bd5f0da359fd54c5164b376007d182a9fabde))
### Refactoring
- **uuid-generator**: prevent NaN in quantity (6fb4994)
### Chores
- **release**: create a github release on new version (dbad773)
- **version**: reset CHANGELOG content to support new format (85cb0ff)
### Refactors
* **menu:** removed burger menu icon tooltip ([09abffb](https://github.com/CorentinTh/it-tools/commit/09abffbcf9b09cb5adc34f8754b019d0c8b60854))
### [2.0.2](https://github.com/CorentinTh/it-tools/compare/v2.0.1...v2.0.2) (2022-04-18)
### Bug Fixes
* **git-memo:** pre scroll on overflow ([4fc303e](https://github.com/CorentinTh/it-tools/commit/4fc303e5e3f0bef9201cc002963e244a5d3be7b5))
* **menu:** menu auto closed on mobile ([71f79a5](https://github.com/CorentinTh/it-tools/commit/71f79a5bbfe0dd5451a435c0a55e8b77ee7d3848))
* **qr-code:** responsive layout ([cbf0b3d](https://github.com/CorentinTh/it-tools/commit/cbf0b3d6995e47d371a8fbcfccd65ba304fb08dc))
### Refactors
* **crontab:** list instead of table on small screen ([6b11de2](https://github.com/CorentinTh/it-tools/commit/6b11de258a8039fe7729130ede35d47592be7cbe))
* removed empty sources ([a14cac6](https://github.com/CorentinTh/it-tools/commit/a14cac6d5c5967a47ca76a1d1a420115114c3bbf))
* throw an error object instead of string ([4112fa5](https://github.com/CorentinTh/it-tools/commit/4112fa532e3d4be190d52bf3b11e0d4c3625a402))
### [2.0.1](https://github.com/CorentinTh/it-tools/compare/v2.0.0...v2.0.1) (2022-04-16)
## Version 2023.04.14-f9b77b7
### Features
- **new-tool**: http status codes (8355bd2)
* **config:** added vercel.json ([2e046ad](https://github.com/CorentinTh/it-tools/commit/2e046ad09fed4a55bbf4449e3683a4150839c461))
### Refactoring
- **uuid-generator**: prevent NaN in quantity (6fb4994)
### Chores
- **release**: create a github release on new version (f9b77b7)
- **version**: reset CHANGELOG content to support new format (85cb0ff)
### Bug Fixes
* remove duplicate property ([d066319](https://github.com/CorentinTh/it-tools/commit/d066319b45dee35df0212c7ff407013bd7449ae3))
* **style:** url encode/decode layout ([34480b4](https://github.com/CorentinTh/it-tools/commit/34480b4e25ffc33536b03a0ba711c480219ad553))
### Documentation
* updated description ([70a3df0](https://github.com/CorentinTh/it-tools/commit/70a3df044ea86ac35c1839ac5ab624f694fdd845))
### Refactors
* clean imports ([724e142](https://github.com/CorentinTh/it-tools/commit/724e142222202798ea3df7d0fb34da1e7a5216a1))
* lint fix ([a58ae24](https://github.com/CorentinTh/it-tools/commit/a58ae24d9409728ac12fb780f2c64643087de5be))
* ref name ([5828085](https://github.com/CorentinTh/it-tools/commit/582808597c6aadf0feb48f6aae0a29b839e0dd54))
## 2.0.0 (2022-04-16)
## Version 2023.04.14-2f0d239
### Features
- **new-tool**: http status codes (8355bd2)
* **a11y:** aria-label on icon button ([5f50275](https://github.com/CorentinTh/it-tools/commit/5f502755d69ab21a78d9256db8a1c64f1ab82c2a))
* added commit short sha ([668625c](https://github.com/CorentinTh/it-tools/commit/668625c6dab6e8b98f363df6c0aa3bf00a3afaa4))
* added plausible tracker ([0808920](https://github.com/CorentinTh/it-tools/commit/0808920951b55c938537f33353a37ece96b04084))
* added twitter link ([d126abc](https://github.com/CorentinTh/it-tools/commit/d126abc7b12a9fce778fe9883e44dca581509778))
* footer in sider ([3f03850](https://github.com/CorentinTh/it-tools/commit/3f038503dd705ba3a5562a1e8f85a3b0e7d0be5b))
* **layout:** menu category ([9c9be9e](https://github.com/CorentinTh/it-tools/commit/9c9be9e2e2e2c856d1af1df9d9d37a64460cd82b))
* mobile friendly menu ([1e67fa6](https://github.com/CorentinTh/it-tools/commit/1e67fa6e0bede8c055d9e4cb9bf7f97423bc9bdf))
* **navbar:** added github link ([d4e226e](https://github.com/CorentinTh/it-tools/commit/d4e226e09face78da794fa7e676eef85d05dde75))
* **nav:** navigation tooltips ([b892f50](https://github.com/CorentinTh/it-tools/commit/b892f50cd633d42e6261be208bd077d92d336afb))
* **page:** added 404 page ([3db4f91](https://github.com/CorentinTh/it-tools/commit/3db4f91c27a2ab37bb23d8feb77b6dffa9a92977))
* **page:** home page layout ([57fd14a](https://github.com/CorentinTh/it-tools/commit/57fd14a199a253f49f3c53810490e5d31512b261))
* persistent theme selection fallback to prefered theme ([40e9af0](https://github.com/CorentinTh/it-tools/commit/40e9af06cf28b7348152f8ec3898fa2b27ec0b21))
* **router:** added legacy routes redirections ([dbce46b](https://github.com/CorentinTh/it-tools/commit/dbce46b470b0187a395cdd350a023641c6319582))
* search-bar ([e8594de](https://github.com/CorentinTh/it-tools/commit/e8594de7b45102b8bc1cfb82d0839e3722d9c4c2))
* **search:** round and clearable searchbar ([b112f5f](https://github.com/CorentinTh/it-tools/commit/b112f5f226c6b03151bbeb4fc607e449c444e667))
* **seo:** added robots.txt and humans.txt ([cd9a3bc](https://github.com/CorentinTh/it-tools/commit/cd9a3bc9b10cf7363301e9a0d0b17f38ea640e0c))
* **seo:** added title + description ([5f74037](https://github.com/CorentinTh/it-tools/commit/5f74037105c5e8efc5bdad2261597458cfcf26d3))
* **seo:** pwa and icons ([b7193e8](https://github.com/CorentinTh/it-tools/commit/b7193e838ba83d0548211cff922e107a1f11f90f))
* **share:** social image ([39746e0](https://github.com/CorentinTh/it-tools/commit/39746e07c53c22ac132ad2aaf25dd71bb6458cde))
* **style:** dark mode ([3e92b7f](https://github.com/CorentinTh/it-tools/commit/3e92b7f1e04a709df231fce22801b55619e8faab))
* **style:** theme overrides ([d542688](https://github.com/CorentinTh/it-tools/commit/d542688664cc9c675d1d26f4278a25f1b9e3f28d))
* **tool:** add lch in color converter ([b5243c4](https://github.com/CorentinTh/it-tools/commit/b5243c43638f37a2d727b015bba61fab0d1b9fe9))
* **tool:** added token generator ([40dec52](https://github.com/CorentinTh/it-tools/commit/40dec52c8467fd27eb8f3857ed72746ebaa4f509))
* **tool:** base converter ([034c686](https://github.com/CorentinTh/it-tools/commit/034c686896d0443ea587cd152535b2227234c011))
* **tool:** base64 string converter ([203b6a9](https://github.com/CorentinTh/it-tools/commit/203b6a9d73dcb30182b130de59920534e18b76b4))
* **tool:** bip39-generator ([d55329f](https://github.com/CorentinTh/it-tools/commit/d55329f3abc3d3f8ad48def7d7f63b44cd768e27))
* **tool:** bip39-generator ([765c010](https://github.com/CorentinTh/it-tools/commit/765c010700c07b2809daef0e7c694ac265ce9ddc))
* **tool:** case converter ([7a7372d](https://github.com/CorentinTh/it-tools/commit/7a7372df191abc7ecd3fee7234d4de7aaaba03f6))
* **tool:** color converter ([4e50b7a](https://github.com/CorentinTh/it-tools/commit/4e50b7a973e950819a52c127db2a754838cbbf8e))
* **tool:** crontab generator ([358ff45](https://github.com/CorentinTh/it-tools/commit/358ff45ae1d9822b8a7c342515f668d25b7128b5))
* **tool:** date-time converter ([2d9cb20](https://github.com/CorentinTh/it-tools/commit/2d9cb209b377326f4bf62067db7d5ad0c7eb7bde))
* **tool:** encryption ([888ab2c](https://github.com/CorentinTh/it-tools/commit/888ab2cf378597e2880b6dd6a013f3bc192f2b1a))
* **tool:** git memo ([5cd9997](https://github.com/CorentinTh/it-tools/commit/5cd9997a845f6d5f82d3ae74d3ec12603224517d))
* **tool:** lorem ipsum generator ([5dcb2ed](https://github.com/CorentinTh/it-tools/commit/5dcb2ed95c318ea1c4134da207c844672d0fbbd8))
* **tool:** qr-code generator ([5582d75](https://github.com/CorentinTh/it-tools/commit/5582d75927b560d9259929c787c0809634d1f8ae))
* **tool:** random port generator ([7c540f1](https://github.com/CorentinTh/it-tools/commit/7c540f1208da749c3932aab8f2c392048c4546ae))
* **tool:** roman-arabic numbers converter ([655019c](https://github.com/CorentinTh/it-tools/commit/655019cf23babcec2a2f1e03cac87744e3139304))
* **tool:** text hash ([0f3b744](https://github.com/CorentinTh/it-tools/commit/0f3b7445ad1f945d9b364476147bf824ac309a6c))
* **tool:** text statistics ([0a7c325](https://github.com/CorentinTh/it-tools/commit/0a7c3252e36a4769eedaaec4524b4ee2ae2b19c7))
* **tool:** url encode/decode ([afac566](https://github.com/CorentinTh/it-tools/commit/afac5664c802c8480fe2c457bcfb7f5e26829cdf))
* **tool:** uuid v4 generator ([3ae6114](https://github.com/CorentinTh/it-tools/commit/3ae61147a94791987e9e326b19063579976d8dc0))
* **ux:** copyable input ([1859a9a](https://github.com/CorentinTh/it-tools/commit/1859a9a174010789dcd7ecefb2451e1de7b60b4c))
### Refactoring
- **uuid-generator**: prevent NaN in quantity (6fb4994)
### Chores
- **release**: create a github release on new version (2f0d239)
- **version**: reset CHANGELOG content to support new format (85cb0ff)
### Bug Fixes
## Version 2023.04.14-474cae4
* **hash-text:** added missing toString() ([4ca5fce](https://github.com/CorentinTh/it-tools/commit/4ca5fce911c3312d56bca1ffba863b2f37841c9e))
* **hash-text:** correct copy message ([bab92ef](https://github.com/CorentinTh/it-tools/commit/bab92ef84f66372df40ce385c2949518ed158427))
* removed global define ([889d594](https://github.com/CorentinTh/it-tools/commit/889d59499212a449ee460c68c480648e337a7ecb))
* **style:** working dark mode persistence ([3ae8728](https://github.com/CorentinTh/it-tools/commit/3ae872847b00d65e4e2e629775d479a3333450f1))
* **validation:** proper rules ([11d8110](https://github.com/CorentinTh/it-tools/commit/11d8110226e22e30ae16d297628c1d252a93be9e))
### Features
- **new-tool**: http status codes (8355bd2)
### Refactoring
- **uuid-generator**: prevent NaN in quantity (6fb4994)
### Refactors
### Chores
- **release**: create a github release on new version (474cae4)
- **version**: reset CHANGELOG content to support new format (85cb0ff)
* better icon ([0af7d81](https://github.com/CorentinTh/it-tools/commit/0af7d81abd987aa5d1b0321c25a65131d978e929))
* **clean:** removed extra console.log ([82606f6](https://github.com/CorentinTh/it-tools/commit/82606f6a477fce2041ab33adc7e95bcba4343e2b))
* embeded sider scrollbar ([f872972](https://github.com/CorentinTh/it-tools/commit/f872972e69aeb4fde4c17f0c122ca3fd4aa1c56c))
* icon sizes ([9bb7fc4](https://github.com/CorentinTh/it-tools/commit/9bb7fc47aa70bdc5083d0883f1496fac63f812ea))
* menu option key ([390ef93](https://github.com/CorentinTh/it-tools/commit/390ef93232dc1b448022a0c09d36367adad9d221))
* **page:** removed unused import ([f70fce6](https://github.com/CorentinTh/it-tools/commit/f70fce65e20989eb19b0f0976e756a43edf02e9d))
* removed theme editor ([8559fbd](https://github.com/CorentinTh/it-tools/commit/8559fbd7744fe82b7702a5c0eb77a8d627c5a73d))
* removed unused files ([c1e7669](https://github.com/CorentinTh/it-tools/commit/c1e76695e4a16b8312ab6031a1bdfb6368946677))
* removed unused files ([8d9f924](https://github.com/CorentinTh/it-tools/commit/8d9f92417744a5fbd9b4108e851005f23de18b53))
* **style:** cleaner layout ([1d09a01](https://github.com/CorentinTh/it-tools/commit/1d09a01bb25088493cc9b7f2cb7f8a8aa69ac9e9))
* **style:** improve style for tool-card ([65a6896](https://github.com/CorentinTh/it-tools/commit/65a6896563d16f30420424e274bd306e3e9182c8))
* **style:** label width ([fd4426d](https://github.com/CorentinTh/it-tools/commit/fd4426d246ada553528759f761c8192df85c0d44))
* **style:** menu item height ([8951e87](https://github.com/CorentinTh/it-tools/commit/8951e87c143fda74be32bae5b28e009556d7086e))
* **style:** menu scrollbar ([483cf66](https://github.com/CorentinTh/it-tools/commit/483cf66db992169d361487c8461938810793b978))
* **style:** port display ([2632f24](https://github.com/CorentinTh/it-tools/commit/2632f24cc89af7dd12f7a0c1a8b58983a1bb78d8))
* **style:** removed extra br ([b44539c](https://github.com/CorentinTh/it-tools/commit/b44539c1820defbaaa6dfe83a76c72982a641971))
* **style:** replaced scss style block to less ([655d9d2](https://github.com/CorentinTh/it-tools/commit/655d9d22e3136bdf1dee29310ab04cf38596bdc8))
* **style:** responsive layout ([2df3f53](https://github.com/CorentinTh/it-tools/commit/2df3f53b78bbe419763fd359788a4b0b5710e4b7))
* **style:** updated linter config ([6b58ec5](https://github.com/CorentinTh/it-tools/commit/6b58ec554a0de91139f16d67cec42536d093d5fb))
## Version v2023.4.13-dce9ff9
### Documentation
* added new tool creation procedure ([8177883](https://github.com/CorentinTh/it-tools/commit/81778834e6a79725c42eae1772935682ce7580c6))
* updated readme ([1134e0b](https://github.com/CorentinTh/it-tools/commit/1134e0b822edbc25ce9ff83007bf5d331a1becbd))
_Diff not available_

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# build stage
FROM node:lts-alpine AS build-stage
WORKDIR /app
COPY . .
RUN npm install -g pnpm
RUN pnpm i --frozen-lockfile
RUN pnpm build
# production stage
FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -6,50 +6,93 @@ Useful tools for developer and people working in IT. [Have a look !](https://it-
Please check the [issues](https://github.com/CorentinTh/it-tools/issues) to see if some feature listed to be implemented.
You have an idea of a tool? Submit a [feature request](https://github.com/CorentinTh/it-tools/issues/new?assignees=corentinth&labels=&template=feature_request.md&title=)!
You have an idea of a tool? Submit a [feature request](https://github.com/CorentinTh/it-tools/issues/new/choose)!
## Self host
Self host solutions for your homelab
**From docker hub:**
```sh
docker run -d --name it-tools --restart unless-stopped -p 8080:80 corentinth/it-tools:latest
```
**From github packages:**
```sh
docker run -d --name it-tools --restart unless-stopped -p 8080:80 ghcr.io/corentinth/it-tools:latest
```
**Other solutions:**
- [Cloudron](https://www.cloudron.io/store/tech.ittools.cloudron.html)
- [Tipi](https://www.runtipi.io/docs/apps-available)
- [Unraid](https://unraid.net/community/apps?q=it-tools)
## Contribute
### Recommended IDE Setup
[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).
[VSCode](https://code.visualstudio.com/) with the following extensions:
### Node version
- [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).
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [i18n Ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally)
Ensure you have the correct node/npm version
with the following settings:
```sh
nvm use
```json
{
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"i18n-ally.localesPaths": ["locales", "src/tools/*/locales"],
"i18n-ally.keystyle": "nested"
}
```
### Type Support for `.vue` Imports in TS
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.
### Project Setup
```sh
npm install
pnpm install
```
#### Compile and Hot-Reload for Development
### Compile and Hot-Reload for Development
```sh
npm run dev
pnpm dev
```
#### Type-Check, Compile and Minify for Production
### Type-Check, Compile and Minify for Production
```sh
npm run build
pnpm build
```
#### Run Unit Tests with [Vitest](https://vitest.dev/)
### Run Unit Tests with [Vitest](https://vitest.dev/)
```sh
npm run test
pnpm test
```
#### Lint with [ESLint](https://eslint.org/)
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
pnpm lint
```
### Create a new tool
@@ -57,10 +100,10 @@ npm run lint
To create a new tool, there is a script that generate the boilerplate of the new tool, simply run:
```sh
node scripts/create-tool.mjs my-tool-name
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 inported tool in the proper category and develop the tool.
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.
## Credits
@@ -68,12 +111,9 @@ 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&#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>
## License
This project is under the [GNU GPLv3](LICENSE).

862
auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,862 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
const computedInject: typeof import('@vueuse/core')['computedInject']
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
const createEventHook: typeof import('@vueuse/core')['createEventHook']
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
const effectScope: typeof import('vue')['effectScope']
const extendRef: typeof import('@vueuse/core')['extendRef']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const inject: typeof import('vue')['inject']
const isDefined: typeof import('@vueuse/core')['isDefined']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onLongPress: typeof import('@vueuse/core')['onLongPress']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const provide: typeof import('vue')['provide']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
const reactivePick: typeof import('@vueuse/core')['reactivePick']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
const refDebounced: typeof import('@vueuse/core')['refDebounced']
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const syncRef: typeof import('@vueuse/core')['syncRef']
const syncRefs: typeof import('@vueuse/core')['syncRefs']
const templateRef: typeof import('@vueuse/core')['templateRef']
const throttledRef: typeof import('@vueuse/core')['throttledRef']
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
const toRaw: typeof import('vue')['toRaw']
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
const unref: typeof import('vue')['unref']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
const useCssVars: typeof import('vue')['useCssVars']
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
const useCycleList: typeof import('@vueuse/core')['useCycleList']
const useDark: typeof import('@vueuse/core')['useDark']
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
const useDebounce: typeof import('@vueuse/core')['useDebounce']
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
const useDialog: typeof import('naive-ui')['useDialog']
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
const useDraggable: typeof import('@vueuse/core')['useDraggable']
const useDropZone: typeof import('@vueuse/core')['useDropZone']
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
const useElementHover: typeof import('@vueuse/core')['useElementHover']
const useElementSize: typeof import('@vueuse/core')['useElementSize']
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
const useEventBus: typeof import('@vueuse/core')['useEventBus']
const useEventListener: typeof import('@vueuse/core')['useEventListener']
const useEventSource: typeof import('@vueuse/core')['useEventSource']
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
const useFocus: typeof import('@vueuse/core')['useFocus']
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
const useFps: typeof import('@vueuse/core')['useFps']
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
const useGamepad: typeof import('@vueuse/core')['useGamepad']
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useI18n: typeof import('vue-i18n')['useI18n']
const useIdle: typeof import('@vueuse/core')['useIdle']
const useImage: typeof import('@vueuse/core')['useImage']
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
const useInterval: typeof import('@vueuse/core')['useInterval']
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLink: typeof import('vue-router')['useLink']
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
const useMemoize: typeof import('@vueuse/core')['useMemoize']
const useMemory: typeof import('@vueuse/core')['useMemory']
const useMessage: typeof import('naive-ui')['useMessage']
const useMounted: typeof import('@vueuse/core')['useMounted']
const useMouse: typeof import('@vueuse/core')['useMouse']
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
const useNetwork: typeof import('@vueuse/core')['useNetwork']
const useNotification: typeof import('naive-ui')['useNotification']
const useNow: typeof import('@vueuse/core')['useNow']
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useScroll: typeof import('@vueuse/core')['useScroll']
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle']
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
const useVModel: typeof import('@vueuse/core')['useVModel']
const useVModels: typeof import('@vueuse/core')['useVModels']
const useVibrate: typeof import('@vueuse/core')['useVibrate']
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
const watch: typeof import('vue')['watch']
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDialog: UnwrapRef<typeof import('naive-ui')['useDialog']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLoadingBar: UnwrapRef<typeof import('naive-ui')['useLoadingBar']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
readonly useMessage: UnwrapRef<typeof import('naive-ui')['useMessage']>
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
readonly useNotification: UnwrapRef<typeof import('naive-ui')['useNotification']>
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDialog: UnwrapRef<typeof import('naive-ui')['useDialog']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLoadingBar: UnwrapRef<typeof import('naive-ui')['useLoadingBar']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
readonly useMessage: UnwrapRef<typeof import('naive-ui')['useMessage']>
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
readonly useNotification: UnwrapRef<typeof import('naive-ui')['useNotification']>
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
}
}

188
components.d.ts vendored Normal file
View File

@@ -0,0 +1,188 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
'404.page': typeof import('./src/pages/404.page.vue')['default']
About: typeof import('./src/pages/About.vue')['default']
App: typeof import('./src/App.vue')['default']
'Base.layout': typeof import('./src/layouts/base.layout.vue')['default']
Base64FileConverter: typeof import('./src/tools/base64-file-converter/base64-file-converter.vue')['default']
Base64StringConverter: typeof import('./src/tools/base64-string-converter/base64-string-converter.vue')['default']
BasicAuthGenerator: typeof import('./src/tools/basic-auth-generator/basic-auth-generator.vue')['default']
Bcrypt: typeof import('./src/tools/bcrypt/bcrypt.vue')['default']
BenchmarkBuilder: typeof import('./src/tools/benchmark-builder/benchmark-builder.vue')['default']
Bip39Generator: typeof import('./src/tools/bip39-generator/bip39-generator.vue')['default']
CAlert: typeof import('./src/ui/c-alert/c-alert.vue')['default']
'CAlert.demo': typeof import('./src/ui/c-alert/c-alert.demo.vue')['default']
CameraRecorder: typeof import('./src/tools/camera-recorder/camera-recorder.vue')['default']
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']
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']
ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default']
Chronometer: typeof import('./src/tools/chronometer/chronometer.vue')['default']
CInputText: typeof import('./src/ui/c-input-text/c-input-text.vue')['default']
'CInputText.demo': typeof import('./src/ui/c-input-text/c-input-text.demo.vue')['default']
CLabel: typeof import('./src/ui/c-label/c-label.vue')['default']
CLink: typeof import('./src/ui/c-link/c-link.vue')['default']
'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default']
CModal: typeof import('./src/ui/c-modal/c-modal.vue')['default']
'CModal.demo': typeof import('./src/ui/c-modal/c-modal.demo.vue')['default']
CollapsibleToolMenu: typeof import('./src/components/CollapsibleToolMenu.vue')['default']
ColorConverter: typeof import('./src/tools/color-converter/color-converter.vue')['default']
ColoredCard: typeof import('./src/components/ColoredCard.vue')['default']
CommandPalette: typeof import('./src/modules/command-palette/command-palette.vue')['default']
CommandPaletteOption: typeof import('./src/modules/command-palette/components/command-palette-option.vue')['default']
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']
DateTimeConverter: typeof import('./src/tools/date-time-converter/date-time-converter.vue')['default']
'DemoHome.page': typeof import('./src/ui/demo/demo-home.page.vue')['default']
DemoWrapper: typeof import('./src/ui/demo/demo-wrapper.vue')['default']
DeviceInformation: typeof import('./src/tools/device-information/device-information.vue')['default']
DiffViewer: typeof import('./src/tools/json-diff/diff-viewer/diff-viewer.vue')['default']
DockerRunToDockerComposeConverter: typeof import('./src/tools/docker-run-to-docker-compose-converter/docker-run-to-docker-compose-converter.vue')['default']
DynamicValues: typeof import('./src/tools/benchmark-builder/dynamic-values.vue')['default']
Editor: typeof import('./src/tools/html-wysiwyg-editor/editor/editor.vue')['default']
EmojiCard: typeof import('./src/tools/emoji-picker/emoji-card.vue')['default']
EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default']
EmojiPicker: typeof import('./src/tools/emoji-picker/emoji-picker.vue')['default']
Encryption: typeof import('./src/tools/encryption/encryption.vue')['default']
EtaCalculator: typeof import('./src/tools/eta-calculator/eta-calculator.vue')['default']
FavoriteButton: typeof import('./src/components/FavoriteButton.vue')['default']
FormatTransformer: typeof import('./src/components/FormatTransformer.vue')['default']
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
HtmlWysiwygEditor: typeof import('./src/tools/html-wysiwyg-editor/html-wysiwyg-editor.vue')['default']
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default']
'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default']
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
IconMdiCamera: typeof import('~icons/mdi/camera')['default']
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
IconMdiDownload: typeof import('~icons/mdi/download')['default']
IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiPause: typeof import('~icons/mdi/pause')['default']
IconMdiPlay: typeof import('~icons/mdi/play')['default']
IconMdiRecord: typeof import('~icons/mdi/record')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiSearch: typeof import('~icons/mdi/search')['default']
IconMdiVideo: typeof import('~icons/mdi/video')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default']
Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default']
Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default']
Ipv6UlaGenerator: typeof import('./src/tools/ipv6-ula-generator/ipv6-ula-generator.vue')['default']
JsonDiff: typeof import('./src/tools/json-diff/json-diff.vue')['default']
JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default']
JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.vue')['default']
JsonToToml: typeof import('./src/tools/json-to-toml/json-to-toml.vue')['default']
JsonToYaml: typeof import('./src/tools/json-to-yaml-converter/json-to-yaml.vue')['default']
JsonViewer: typeof import('./src/tools/json-viewer/json-viewer.vue')['default']
JwtParser: typeof import('./src/tools/jwt-parser/jwt-parser.vue')['default']
KeycodeInfo: typeof import('./src/tools/keycode-info/keycode-info.vue')['default']
ListConverter: typeof import('./src/tools/list-converter/list-converter.vue')['default']
LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default']
MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default']
MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default']
MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default']
MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default']
MenuIconItem: typeof import('./src/components/MenuIconItem.vue')['default']
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDivider: typeof import('naive-ui')['NDivider']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NH1: typeof import('naive-ui')['NH1']
NH2: typeof import('naive-ui')['NH2']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import('naive-ui')['NImage']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
NP: typeof import('naive-ui')['NP']
NPageHeader: typeof import('naive-ui')['NPageHeader']
NProgress: typeof import('naive-ui')['NProgress']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTable: typeof import('naive-ui')['NTable']
NTag: typeof import('naive-ui')['NTag']
NTooltip: typeof import('naive-ui')['NTooltip']
NUpload: typeof import('naive-ui')['NUpload']
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
PercentageCalculator: typeof import('./src/tools/percentage-calculator/percentage-calculator.vue')['default']
PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default']
QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default']
RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default']
ResultRow: typeof import('./src/tools/ipv4-range-expander/result-row.vue')['default']
RomanNumeralConverter: typeof import('./src/tools/roman-numeral-converter/roman-numeral-converter.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']
StringObfuscator: typeof import('./src/tools/string-obfuscator/string-obfuscator.vue')['default']
SvgPlaceholderGenerator: typeof import('./src/tools/svg-placeholder-generator/svg-placeholder-generator.vue')['default']
TemperatureConverter: typeof import('./src/tools/temperature-converter/temperature-converter.vue')['default']
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']
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']
TomlToJson: typeof import('./src/tools/toml-to-json/toml-to-json.vue')['default']
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']
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']
UserAgentResultCards: typeof import('./src/tools/user-agent-parser/user-agent-result-cards.vue')['default']
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default']
YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default']
YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default']
}
}

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>IT Tools - Handy online tools for developers</title>
<meta itemprop="name" content="IT Tools - Handy online tools for developers" />
@@ -14,13 +14,13 @@
itemprop="description"
content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
/>
<link rel="author" href="/humans.txt" />
<link rel="author" href="humans.txt" />
<link rel="canonical" href="https://it-tools.tech" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#18a058" />
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png" />
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#18a058" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
@@ -31,17 +31,19 @@
property="og:description"
content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
/>
<meta property="og:image" content="/banner.png" />
<meta property="og:image" content="https://it-tools.tech/banner.png?v=2" />
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content="it-tools.tech" />
<meta property="twitter:url" content="https://it-tools.tech/" />
<meta name="twitter:site" content="@ittoolsdottech" />
<meta name="twitter:creator" content="@cthmsst" />
<meta name="twitter:title" content="IT Tools - Handy online tools for developers" />
<meta
name="twitter:description"
content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
/>
<meta name="twitter:image" content="/banner.png" />
<meta name="twitter:image" content="https://it-tools.tech/banner.png?v=2" />
<meta name="twitter:image:alt" content="IT Tools - Handy online tools for developers" />
</head>
<body>
<div id="app"></div>

4
locales/en.yml Normal file
View File

@@ -0,0 +1,4 @@
home:
categories:
newestTools: Newest tools

3
locales/fr.yml Normal file
View File

@@ -0,0 +1,3 @@
home:
categories:
newestTools: "Nouveaux outils"

4
netlify.toml Normal file
View File

@@ -0,0 +1,4 @@
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

10
nginx.conf Normal file
View File

@@ -0,0 +1,10 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}

19645
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "it-tools",
"version": "2.9.1",
"version": "2023.8.21-6f93cba",
"description": "Collection of handy online tools for developers, with great UX. ",
"keywords": [
"productivity",
@@ -25,69 +25,109 @@
"preview": "vite preview --port 5050",
"test": "npm run test:unit",
"test:unit": "vitest --environment jsdom",
"test:e2e": "playwright test",
"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",
"release": "standard-version"
"script:create-new-tool": "node scripts/create-tool.mjs",
"release": "node ./scripts/release.mjs"
},
"dependencies": {
"@it-tools/bip39": "^0.0.4",
"@it-tools/oggen": "^1.3.0",
"@sindresorhus/slugify": "^2.2.1",
"@tiptap/pm": "^2.1.7",
"@tiptap/starter-kit": "^2.1.7",
"@tiptap/vue-3": "^2.1.7",
"@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0",
"@vueuse/core": "^8.2.1",
"@vueuse/head": "^0.7.5",
"@vueuse/core": "^10.4.0",
"@vueuse/head": "^1.3.1",
"@vueuse/router": "^10.4.0",
"bcryptjs": "^2.4.3",
"change-case": "^4.1.2",
"colord": "^2.9.2",
"colord": "^2.9.3",
"composerize-ts": "^0.6.2",
"country-code-lookup": "^0.1.0",
"cron-validator": "^1.3.1",
"cronstrue": "^2.2.0",
"cronstrue": "^2.31.0",
"crypto-js": "^4.1.1",
"date-fns": "^2.28.0",
"date-fns": "^2.30.0",
"emojilib": "^3.0.10",
"figue": "^1.2.0",
"highlight.js": "^11.5.1",
"fuse.js": "^6.6.2",
"highlight.js": "^11.8.0",
"iarna-toml-esm": "^3.0.5",
"json5": "^2.2.3",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.10.41",
"lodash": "^4.17.21",
"mathjs": "^10.6.0",
"mathjs": "^11.10.0",
"mime-types": "^2.1.35",
"naive-ui": "^2.31.0",
"pinia": "^2.0.11",
"plausible-tracker": "^0.3.5",
"qrcode": "^1.5.0",
"monaco-editor": "^0.41.0",
"naive-ui": "^2.34.4",
"netmask": "^2.0.2",
"node-forge": "^1.3.1",
"oui": "^12.0.71",
"pinia": "^2.1.6",
"plausible-tracker": "^0.3.8",
"qrcode": "^1.5.3",
"randombytes": "^2.1.0",
"sql-formatter": "^8.2.0",
"uuid": "^8.3.2",
"vue": "^3.2.31",
"vue-router": "^4.0.12"
"sql-formatter": "^12.2.4",
"ua-parser-js": "^1.0.35",
"unicode-emoji-json": "^0.4.0",
"unplugin-auto-import": "^0.16.6",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vue-router": "^4.2.4",
"vue-tsc": "^1.8.8",
"xml-formatter": "^3.5.0",
"yaml": "^2.3.1"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.0",
"@antfu/eslint-config": "^0.41.0",
"@iconify-json/mdi": "^1.1.54",
"@intlify/unplugin-vue-i18n": "^0.12.3",
"@playwright/test": "^1.37.1",
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node18": "^18.2.1",
"@types/bcryptjs": "^2.4.2",
"@types/crypto-js": "^4.1.1",
"@types/jsdom": "^16.2.14",
"@types/jsdom": "^21.1.2",
"@types/lodash": "^4.14.197",
"@types/mime-types": "^2.1.1",
"@types/node": "^16.11.25",
"@types/qrcode": "^1.4.2",
"@types/netmask": "^2.0.2",
"@types/node": "^18.17.11",
"@types/node-forge": "^1.3.4",
"@types/qrcode": "^1.5.1",
"@types/randombytes": "^2.0.0",
"@types/uuid": "^8.3.4",
"@vitejs/plugin-vue": "^2.2.2",
"@vitejs/plugin-vue-jsx": "^1.3.7",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@vue/test-utils": "^2.0.0-rc.18",
"@vue/tsconfig": "^0.1.3",
"c8": "^7.11.0",
"eslint": "^8.5.0",
"eslint-plugin-vue": "^8.2.0",
"jsdom": "^19.0.0",
"less": "^4.1.2",
"prettier": "^2.6.2",
"standard-version": "^9.3.2",
"start-server-and-test": "^1.14.0",
"typescript": "~4.5.5",
"vite": "^2.9.1",
"vite-plugin-md": "^0.12.4",
"vite-plugin-pwa": "^0.11.13",
"vite-svg-loader": "^3.2.0",
"vitest": "^0.13.1",
"vue-tsc": "^0.31.4"
"@types/ua-parser-js": "^0.7.36",
"@types/uuid": "^9.0.2",
"@unocss/eslint-config": "^0.55.3",
"@vitejs/plugin-vue": "^4.3.3",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/compiler-sfc": "^3.3.4",
"@vue/runtime-dom": "^3.3.4",
"@vue/test-utils": "^2.4.1",
"@vue/tsconfig": "^0.4.0",
"c8": "^8.0.1",
"consola": "^3.2.3",
"eslint": "^8.48.0",
"jsdom": "^22.1.0",
"less": "^4.2.0",
"prettier": "^3.0.2",
"typescript": "~5.2.2",
"unocss": "^0.55.3",
"unocss-preset-scrollbar": "^0.2.1",
"unplugin-icons": "^0.16.5",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.4.9",
"vite-plugin-pwa": "^0.16.4",
"vite-plugin-vue-markdown": "^0.23.8",
"vite-svg-loader": "^4.0.0",
"vitest": "^0.34.3",
"workbox-window": "^7.0.0",
"zx": "^7.2.3"
}
}

64
playwright.config.ts Normal file
View File

@@ -0,0 +1,64 @@
import { defineConfig, devices } from '@playwright/test';
const isCI = !!process.env.CI;
const baseUrl = process.env.BASE_URL || 'http://localhost:5050';
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './src',
testMatch: /.*\.e2e\.(spec\.)?ts/,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: isCI,
/* Retry on CI only */
retries: isCI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: isCI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: baseUrl,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
testIdAttribute: 'data-test-id',
locale: 'en-GB',
timezoneId: 'Europe/Paris',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
/* Run your local dev server before starting the tests */
...(isCI
? {}
: {
webServer: {
command: 'npm run preview',
url: 'http://127.0.0.1:5050',
reuseExistingServer: true,
},
}),
});

8974
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 155 KiB

6
renovate.json Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

View File

@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
const currentDirname = dirname(fileURLToPath(import.meta.url));
const toolsDir = join(currentDirname, '..', 'src', 'tools');
// eslint-disable-next-line no-undef
const toolName = process.argv[2];
if (!toolName) {
@@ -28,9 +29,9 @@ createToolFile(
`${toolName}.vue`,
`
<template>
<n-card>
<div>
Lorem ipsum
</n-card>
</div>
</template>
<script setup lang="ts">
@@ -55,6 +56,7 @@ export const tool = defineTool({
keywords: ['${toolName.split('-').join("', '")}'],
component: () => import('./${toolName}.vue'),
icon: ArrowsShuffle,
createdAt: new Date('${new Date().toISOString().split('T')[0]}'),
});
`,
);
@@ -72,6 +74,28 @@ import { expect, describe, it } from 'vitest';
`,
);
createToolFile(
`${toolName}.e2e.spec.ts`,
`
import { test, expect } from '@playwright/test';
test.describe('Tool - ${toolNameTitleCase}', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/${toolName}');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('${toolNameTitleCase} - IT Tools');
});
test('', async ({ page }) => {
});
});
`,
);
const toolsIndex = join(toolsDir, 'index.ts');
const indexContent = await readFile(toolsIndex, { encoding: 'utf-8' }).then((r) => r.split('\n'));

View File

@@ -0,0 +1,6 @@
import { readFile } from 'fs/promises';
const changelogContent = await readFile('./CHANGELOG.md', 'utf-8');
const [, lastChangelog] = changelogContent.split(/^## .*$/gm);
console.log(lastChangelog.trim());

57
scripts/release.mjs Normal file
View File

@@ -0,0 +1,57 @@
import { $, argv } from 'zx';
import { consola } from 'consola';
import { rawCommitsToMarkdown } from './shared/commits.mjs';
import { addToChangelog } from './shared/changelog.mjs';
$.verbose = false;
const isDryRun = argv['dry-run'] ?? false;
const now = new Date();
const currentShortSha = (await $`git rev-parse --short HEAD`).stdout.trim();
const calver = now.toISOString().slice(0, 10).replace(/-/g, '.');
const version = `${calver}-${currentShortSha}`;
const { stdout: rawCommits } = await $`git log --pretty=oneline $(git describe --tags --abbrev=0)..HEAD`;
const markdown = rawCommitsToMarkdown({ rawCommits });
consola.info(`Changelog: \n\n${markdown}\n\n`);
if (isDryRun) {
consola.info(`[dry-run] Not creating version nor tag`);
consola.info('Aborting');
process.exit(0);
}
const shouldContinue = await consola.prompt(
'This script will create a new version and tag, and update the changelog. Continue?',
{
type: 'confirm',
},
);
if (!shouldContinue) {
consola.info('Aborting');
process.exit(0);
}
consola.info('Updating changelog');
await addToChangelog({ changelog: markdown, version });
consola.success('Changelog updated');
try {
consola.info('Committing changelog changes');
await $`git add CHANGELOG.md`;
await $`git commit -m "docs(changelog): update changelog for ${version}"`;
consola.success('Changelog changes committed');
consola.info('Creating version and tag');
await $`npm version ${version} -m "chore(version): release ${version}"`;
consola.info('Npm version released with tag');
} catch (error) {
consola.error(error);
consola.info('Aborting');
process.exit(1);
}

View File

@@ -0,0 +1,15 @@
import { readFile, writeFile } from 'fs/promises';
export { addToChangelog };
async function addToChangelog({ changelog, version, changelogPath = './CHANGELOG.md' }) {
const changelogContent = await readFile(changelogPath, 'utf-8');
const versionTitle = `## Version ${version}`;
if (changelogContent.includes(versionTitle)) {
throw new Error(`Version ${version} already exists in the changelog`);
}
const newChangeLogContent = changelogContent.replace('## ', `${versionTitle}\n\n${changelog}\n\n## `);
await writeFile(changelogPath, newChangeLogContent, 'utf-8');
}

View File

@@ -0,0 +1,54 @@
import _ from 'lodash';
export { rawCommitsToMarkdown };
const commitScopesToHumanReadable = {
build: 'Build system',
chore: 'Chores',
ci: 'Continuous integration',
docs: 'Documentation',
feat: 'Features',
fix: 'Bug fixes',
infra: 'Infrastucture',
perf: 'Performance',
refactor: 'Refactoring',
test: 'Tests',
};
const commitTypesOrder = ['feat', 'fix', 'perf', 'refactor', 'test', 'build', 'ci', 'chore', 'other'];
const getCommitTypeSortIndex = (type) =>
commitTypesOrder.includes(type) ? commitTypesOrder.indexOf(type) : commitTypesOrder.length;
function parseCommitLine(commit) {
const [sha, ...splittedRawMessage] = commit.trim().split(' ');
const rawMessage = splittedRawMessage.join(' ');
const { type, scope, subject } = /^(?<type>.*?)(\((?<scope>.*)\))?: ?(?<subject>.+)$/.exec(rawMessage)?.groups ?? {};
return {
sha: sha.slice(0, 7),
type: type ?? 'other',
scope,
subject: subject ?? rawMessage,
};
}
function commitSectionsToMarkdown({ type, commits }) {
return [
`### ${commitScopesToHumanReadable[type] ?? _.capitalize(type)}`,
...commits.map(({ sha, scope, subject }) => ['-', scope ? `**${scope}**:` : '', subject, `(${sha})`].join(' ')),
].join('\n');
}
function rawCommitsToMarkdown({ rawCommits }) {
return _.chain(rawCommits)
.trim()
.split('\n')
.map(parseCommitLine)
.groupBy('type')
.map((commits, type) => ({ type, commits }))
.sortBy(({ type }) => getCommitTypeSortIndex(type))
.map(commitSectionsToMarkdown)
.join('\n\n')
.value();
}

View File

@@ -1,9 +1,8 @@
<script setup lang="ts">
import { layouts } from './layouts';
import { computed } from 'vue';
import { useRoute, RouterView } from 'vue-router';
import { RouterView, useRoute } from 'vue-router';
import { NGlobalStyle, NMessageProvider, NNotificationProvider, darkTheme } from 'naive-ui';
import { darkThemeOverrides, lightThemeOverrides } from './themes';
import { darkTheme, NGlobalStyle, NMessageProvider } from 'naive-ui';
import { layouts } from './layouts';
import { useStyleStore } from './stores/style.store';
const route = useRoute();
@@ -16,12 +15,14 @@ const themeOverrides = computed(() => (styleStore.isDarkTheme ? darkThemeOverrid
<template>
<n-config-provider :theme="theme" :theme-overrides="themeOverrides">
<n-global-style />
<n-message-provider placement="bottom">
<component :is="layout">
<router-view />
</component>
</n-message-provider>
<NGlobalStyle />
<NMessageProvider placement="bottom">
<NNotificationProvider placement="bottom-right">
<component :is="layout">
<RouterView />
</component>
</NNotificationProvider>
</NMessageProvider>
</n-config-provider>
</template>

View File

@@ -0,0 +1,114 @@
<script setup lang="ts">
import { useStorage } from '@vueuse/core';
import { useThemeVars } from 'naive-ui';
import { RouterLink, useRoute } from 'vue-router';
import MenuIconItem from './MenuIconItem.vue';
import type { Tool, ToolCategory } from '@/tools/tools.types';
const props = withDefaults(defineProps<{ toolsByCategory?: ToolCategory[] }>(), { toolsByCategory: () => [] });
const { toolsByCategory } = toRefs(props);
const route = useRoute();
const makeLabel = (tool: Tool) => () => h(RouterLink, { to: tool.path }, { default: () => tool.name });
const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool });
const collapsedCategories = useStorage<Record<string, boolean>>(
'menu-tool-option:collapsed-categories',
{},
undefined,
{
deep: true,
serializer: {
read: v => (v ? JSON.parse(v) : null),
write: v => JSON.stringify(v),
},
},
);
function toggleCategoryCollapse({ name }: { name: string }) {
collapsedCategories.value[name] = !collapsedCategories.value[name];
}
const menuOptions = computed(() =>
toolsByCategory.value.map(({ name, components }) => ({
name,
isCollapsed: collapsedCategories.value[name],
tools: components.map(tool => ({
label: makeLabel(tool),
icon: makeIcon(tool),
key: tool.name,
})),
})),
);
const themeVars = useThemeVars();
</script>
<template>
<div v-for="{ name, tools, isCollapsed } of menuOptions" :key="name">
<div ml-6px mt-12px flex cursor-pointer items-center op-60 @click="toggleCategoryCollapse({ name })">
<span :class="{ 'rotate-0': isCollapsed, 'rotate-90': !isCollapsed }" text-16px lh-1 op-50 transition-transform>
<icon-mdi-chevron-right />
</span>
<span ml-8px text-13px>
{{ name }}
</span>
</div>
<n-collapse-transition :show="!isCollapsed">
<div class="menu-wrapper">
<div class="toggle-bar" @click="toggleCategoryCollapse({ name })" />
<n-menu
class="menu"
:value="route.name as string"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="tools"
:indent="8"
:default-expand-all="true"
/>
</div>
</n-collapse-transition>
</div>
</template>
<style scoped lang="less">
.menu-wrapper {
display: flex;
flex-direction: row;
.menu {
flex: 1;
margin-bottom: 5px;
::v-deep(.n-menu-item-content::before) {
left: 0;
right: 13px;
}
}
.toggle-bar {
width: 24px;
opacity: 0.1;
transition: opacity ease 0.2s;
position: relative;
cursor: pointer;
&::before {
width: 2px;
height: 100%;
content: ' ';
background-color: v-bind('themeVars.textColor3');
border-radius: 2px;
position: absolute;
top: 0;
left: 14px;
}
&:hover {
opacity: 0.5;
}
}
}
</style>

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
import type { Component } from 'vue';
const props = defineProps<{ icon: Component; title: string }>();
const { icon, title } = toRefs(props);
</script>
<template>
<c-card class="colored-card">
<n-icon class="icon" size="40" :component="icon" />
<n-h3 class="title">
<n-ellipsis>{{ title }}</n-ellipsis>
</n-h3>
<div class="description">
<n-ellipsis :line-clamp="2" :tooltip="false">
<slot />
</n-ellipsis>
</div>
</c-card>
</template>
<style lang="less" scoped>
.colored-card {
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;
border: none;
.icon {
opacity: 0.7;
}
.title {
color: #fff;
margin: 5px 0;
}
.description {
opacity: 0.8;
margin: 5px 0;
::v-deep(a) {
color: inherit;
text-decoration: underline;
font-weight: bold;
transition: color ease 0.2s;
&:hover {
color: rgb(20, 20, 20);
}
}
}
}
</style>

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
import { FavoriteFilled } from '@vicons/material';
import { useToolStore } from '@/tools/tools.store';
import type { Tool } from '@/tools/tools.types';
const props = defineProps<{ tool: Tool }>();
const toolStore = useToolStore();
const { tool } = toRefs(props);
const isFavorite = computed(() => toolStore.isToolFavorite({ tool }));
const buttonType = computed(() => (isFavorite.value ? 'primary' : 'default'));
function toggleFavorite(event: MouseEvent) {
event.preventDefault();
if (toolStore.isToolFavorite({ tool })) {
toolStore.removeToolFromFavorites({ tool });
return;
}
toolStore.addToolToFavorites({ tool });
}
</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>
</template>

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
import _ from 'lodash';
import type { UseValidationRule } from '@/composable/validation';
import CInputText from '@/ui/c-input-text/c-input-text.vue';
const props = withDefaults(
defineProps<{
transformer?: (v: string) => string
inputValidationRules?: UseValidationRule<string>[]
inputLabel?: string
inputPlaceholder?: string
inputDefault?: string
outputLabel?: string
outputLanguage?: string
}>(),
{
transformer: _.identity,
inputValidationRules: () => [],
inputLabel: 'Input',
inputDefault: '',
inputPlaceholder: 'Input...',
outputLabel: 'Output',
outputLanguage: '',
},
);
const { transformer, inputValidationRules, inputLabel, outputLabel, outputLanguage, inputPlaceholder, inputDefault }
= toRefs(props);
const inputElement = ref<typeof CInputText>();
const input = ref(inputDefault.value);
const output = computed(() => transformer.value(input.value));
</script>
<template>
<CInputText
ref="inputElement"
v-model:value="input"
:placeholder="inputPlaceholder"
:label="inputLabel"
rows="20"
autosize
raw-text
multiline
test-id="input"
:validation-rules="inputValidationRules"
monospace
/>
<div>
<div mb-5px>
{{ outputLabel }}
</div>
<textarea-copyable :value="output" :language="outputLanguage" :follow-height-of="inputElement?.inputWrapperRef" />
</div>
</template>

View File

@@ -1,43 +1,26 @@
<template>
<n-input v-model:value="value">
<template #suffix>
<n-tooltip trigger="hover">
<template #trigger>
<n-button quaternary circle @click="onCopyClicked">
<n-icon :component="ContentCopyFilled" />
</n-button>
</template>
{{ tooltipText }}
</n-tooltip>
</template>
</n-input>
</template>
<script setup lang="ts">
import { useVModel, useClipboard } from '@vueuse/core';
import { ContentCopyFilled } from '@vicons/material';
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { useCopy } from '@/composable/copy';
const props = defineProps<{ value: string }>();
const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit);
const tooltipText = ref('Copy to clipboard');
const { copy } = useClipboard({ source: value });
function onCopyClicked() {
copy();
tooltipText.value = 'Copied !';
setTimeout(() => {
tooltipText.value = 'Copy to clipboard';
}, 2000);
}
const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : 'Copy to clipboard');
</script>
<style scoped>
::v-deep(.n-input-wrapper) {
padding-right: 5px;
}
</style>
<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>
</template>
</c-input-text>
</template>

View File

@@ -1,21 +1,20 @@
<template>
<div class="menu-icon-item">
<n-icon :component="tool.icon" />
<div v-if="tool.isNew" class="badge"></div>
</div>
</template>
<script setup lang="ts">
import type { ITool } from '@/tools/tool';
import { useThemeVars } from 'naive-ui';
import { toRefs } from 'vue';
import type { Tool } from '@/tools/tools.types';
const props = defineProps<{ tool: ITool }>();
const props = defineProps<{ tool: Tool }>();
const { tool } = toRefs(props);
const theme = useThemeVars();
</script>
<template>
<div class="menu-icon-item">
<n-icon :component="tool.icon" />
<div v-if="tool.isNew" class="badge" />
</div>
</template>
<style lang="less" scoped>
.menu-icon-item {
position: relative;

View File

@@ -1,3 +1,11 @@
<script setup lang="ts">
import { useStyleStore } from '@/stores/style.store';
const styleStore = useStyleStore();
const { isMenuCollapsed, isSmallScreen } = toRefs(styleStore);
const siderPosition = computed(() => (isSmallScreen.value ? 'absolute' : 'static'));
</script>
<template>
<n-layout has-sider>
<n-layout-sider
@@ -19,15 +27,6 @@
</n-layout>
</template>
<script setup lang="ts">
import { useStyleStore } from '@/stores/style.store';
import { toRefs, computed } from 'vue';
const styleStore = useStyleStore();
const { isMenuCollapsed, isSmallScreen } = toRefs(styleStore);
const siderPosition = computed(() => (isSmallScreen.value ? 'absolute' : 'static'));
</script>
<style lang="less" scoped>
.overlay {
position: absolute;

View File

@@ -1,71 +1,64 @@
<script setup lang="ts">
import { BrandGithub, BrandTwitter, InfoCircle, Moon, Sun } from '@vicons/tabler';
import { useStyleStore } from '@/stores/style.store';
const styleStore = useStyleStore();
const { isDarkTheme } = toRefs(styleStore);
</script>
<template>
<n-tooltip trigger="hover">
<template #trigger>
<n-button
size="large"
<c-button
circle
quaternary
tag="a"
variant="text"
href="https://github.com/CorentinTh/it-tools"
rel="noopener"
target="_blank"
aria-label="IT-Tools' github repository"
rel="noopener noreferrer"
aria-label="IT-Tools' GitHub repository"
>
<n-icon size="25" :component="BrandGithub" />
</n-button>
</c-button>
</template>
Github repository
</n-tooltip>
<n-tooltip trigger="hover">
<template #trigger>
<n-button
size="large"
<c-button
circle
quaternary
tag="a"
variant="text"
href="https://twitter.com/ittoolsdottech"
rel="noopener"
target="_blank"
aria-label="IT Tools' twitter account"
aria-label="IT Tools' Twitter account"
>
<n-icon size="25" :component="BrandTwitter" />
</n-button>
</c-button>
</template>
IT Tools' twitter account
IT Tools' Twitter account
</n-tooltip>
<router-link to="/about" #="{ navigate, href }" custom>
<n-tooltip trigger="hover">
<template #trigger>
<n-button tag="a" :href="href" circle quaternary size="large" aria-label="About" @click="navigate">
<n-icon size="25" :component="InfoCircle" />
</n-button>
</template>
About
</n-tooltip>
</router-link>
<n-tooltip trigger="hover">
<template #trigger>
<n-button size="large" circle quaternary aria-label="Toggle dark/light mode" @click="isDarkTheme = !isDarkTheme">
<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" />
</n-button>
</c-button>
</template>
<span v-if="isDarkTheme">Light mode</span>
<span v-else>Dark mode</span>
</n-tooltip>
</template>
<script setup lang="ts">
import { useStyleStore } from '@/stores/style.store';
import { BrandGithub, BrandTwitter, InfoCircle, Moon, Sun } from '@vicons/tabler';
import { toRefs } from 'vue';
const styleStore = useStyleStore();
const { isDarkTheme } = toRefs(styleStore);
</script>
<style lang="less" scoped>
.n-button {
&:not(:last-child) {

View File

@@ -1,81 +0,0 @@
<script lang="ts" setup>
import { tools } from '@/tools';
import { SearchRound } from '@vicons/material';
import { useMagicKeys, whenever } from '@vueuse/core';
import { deburr } from 'lodash';
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const queryString = ref('');
const cleanString = (s: string) => deburr(s.trim().toLowerCase());
const searchableTools = tools.map(({ name, description, keywords, path }) => ({
searchableText: [name, description, ...keywords].map(cleanString).join(' '),
path,
name,
}));
const options = computed(() => {
const query = cleanString(queryString.value);
return searchableTools
.filter(({ searchableText }) => searchableText.includes(query))
.map(({ name, path }) => ({ label: name, value: path }));
});
function onSelect(path: string) {
router.push(path);
queryString.value = '';
}
const focusTarget = ref();
const keys = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.ctrlKey && e.key === 'k' && e.type === 'keydown') {
e.preventDefault();
}
},
});
whenever(keys.ctrl_k, () => {
focusTarget.value.focus();
});
</script>
<template>
<div class="search-bar">
<n-auto-complete
v-model:value="queryString"
:options="options"
:input-props="{ autocomplete: 'disabled' }"
:on-select="onSelect"
>
<template #default="{ handleInput, handleBlur, handleFocus, value: slotValue }">
<n-input
ref="focusTarget"
round
clearable
placeholder="Search a tool... [Ctrl + K]"
:value="slotValue"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
>
<template #prefix>
<n-icon :component="SearchRound" />
</template>
</n-input>
</template>
</n-auto-complete>
</div>
</template>
<style lang="less" scoped>
// ::v-deep(.n-input__border) {
// border: none;
// }
</style>

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
import { useCopy } from '@/composable/copy';
const props = withDefaults(defineProps<{ value?: string }>(), { value: '' });
const { value } = toRefs(props);
const initialText = 'Copy to clipboard';
const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
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>
</template>
<style scoped lang="less">
.value {
cursor: pointer;
font-family: monospace;
}
</style>

View File

@@ -0,0 +1,98 @@
<script setup lang="ts">
import { Copy } from '@vicons/tabler';
import { useElementSize } from '@vueuse/core';
import hljs from 'highlight.js/lib/core';
import jsonHljs from 'highlight.js/lib/languages/json';
import sqlHljs from 'highlight.js/lib/languages/sql';
import xmlHljs from 'highlight.js/lib/languages/xml';
import yamlHljs from 'highlight.js/lib/languages/yaml';
import iniHljs from 'highlight.js/lib/languages/ini';
import { useCopy } from '@/composable/copy';
const props = withDefaults(
defineProps<{
value: string
followHeightOf?: HTMLElement | null
language?: string
copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none'
copyMessage?: string
}>(),
{
followHeightOf: null,
language: 'txt',
copyPlacement: 'top-right',
copyMessage: 'Copy to clipboard',
},
);
hljs.registerLanguage('sql', sqlHljs);
hljs.registerLanguage('json', jsonHljs);
hljs.registerLanguage('html', xmlHljs);
hljs.registerLanguage('xml', xmlHljs);
hljs.registerLanguage('yaml', yamlHljs);
hljs.registerLanguage('toml', iniHljs);
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
const { copy, isJustCopied } = useCopy({ source: value, createToast: false });
const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.value);
</script>
<template>
<div style="overflow-x: hidden; width: 100%">
<c-card class="result-card">
<n-scrollbar
x-scrollable
trigger="none"
:style="height ? `min-height: ${height - 40 /* card padding */ + 10 /* negative margin compensation */}px` : ''"
>
<n-config-provider :hljs="hljs">
<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>
</c-card>
<div v-if="copyPlacement === 'outside'" mt-4 flex justify-center>
<c-button @click="copy()">
{{ tooltipText }}
</c-button>
</div>
</div>
</template>
<style lang="less" scoped>
::v-deep(.n-scrollbar) {
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

@@ -1,55 +1,68 @@
<script setup lang="ts">
import { useThemeVars } from 'naive-ui';
import FavoriteButton from './FavoriteButton.vue';
import { useAppTheme } from '@/ui/theme/themes';
import type { Tool } from '@/tools/tools.types';
const props = defineProps<{ tool: Tool & { category: string } }>();
const { tool } = toRefs(props);
const theme = useThemeVars();
const appTheme = useAppTheme();
</script>
<template>
<router-link :to="tool.path">
<n-card class="tool-card">
<n-space justify="space-between" align="center">
<c-card class="tool-card">
<div flex items-center justify-between>
<n-icon class="icon" size="40" :component="tool.icon" />
<n-tag
v-if="tool.isNew"
size="small"
class="badge-new"
round
type="success"
:bordered="false"
:color="{ color: theme.primaryColor, textColor: theme.tagColor }"
>
New
</n-tag>
</n-space>
<div flex items-center gap-8px>
<n-tag
v-if="tool.isNew"
size="small"
class="badge-new"
round
type="success"
:bordered="false"
:color="{ color: theme.primaryColor, textColor: theme.tagColor }"
>
New
</n-tag>
<FavoriteButton :tool="tool" />
</div>
</div>
<n-h3 class="title">
<n-ellipsis>{{ tool.name }}</n-ellipsis>
</n-h3>
<div class="description">
<n-ellipsis :line-clamp="2" :tooltip="false">
<n-ellipsis :line-clamp="2" :tooltip="false" style="min-height: 44.78px">
{{ tool.description }}
<br>&nbsp;
</n-ellipsis>
</div>
</n-card>
</c-card>
</router-link>
</template>
<script setup lang="ts">
import type { ITool } from '@/tools/tool';
import { useThemeVars } from 'naive-ui';
import { toRefs } from 'vue';
const props = defineProps<{ tool: ITool & { category: string } }>();
const { tool } = toRefs(props);
const theme = useThemeVars();
</script>
<style lang="less" scoped>
a {
text-decoration: none;
}
.tool-card {
transition: border-color ease 0.5s;
border-width: 2px !important;
color: transparent;
&:hover {
border-color: var(--n-color-target);
border-color: v-bind('appTheme.primary.colorHover');
}
.icon {
opacity: 0.7;
opacity: 0.6;
color: v-bind('theme.textColorBase');
}
.title {
@@ -57,7 +70,8 @@ a {
}
.description {
opacity: 0.7;
opacity: 0.6;
color: v-bind('theme.textColorBase');
margin: 5px 0;
}
}

View File

@@ -0,0 +1,47 @@
import { computedAsync, watchThrottled } from '@vueuse/core';
import { computed, ref, watch } from 'vue';
export { computedRefreshable, computedRefreshableAsync };
function computedRefreshable<T>(getter: () => T, { throttle }: { throttle?: number } = {}) {
const dirty = ref(true);
let value: T;
const update = () => (dirty.value = true);
if (throttle) {
watchThrottled(getter, update, { throttle });
}
else {
watch(getter, update);
}
const computedValue = computed(() => {
if (dirty.value) {
value = getter();
dirty.value = false;
}
return value;
});
return [computedValue, update] as const;
}
function computedRefreshableAsync<T>(getter: () => Promise<T>, defaultValue?: T) {
const dirty = ref(true);
let value: T;
const update = () => (dirty.value = true);
watch(getter, update);
const computedValue = computedAsync(async () => {
if (dirty.value) {
value = await getter();
dirty.value = false;
}
return value;
}, defaultValue);
return [computedValue, update] as const;
}

View File

@@ -1,15 +1,30 @@
// eslint-disable-next-line no-restricted-imports
import { useClipboard } from '@vueuse/core';
import { useMessage } from 'naive-ui';
import type { Ref } from 'vue';
import type { MaybeRefOrGetter } from 'vue';
export function useCopy({ source, text = 'Copied to the clipboard', createToast = true }: { source?: MaybeRefOrGetter<string>; text?: string; createToast?: boolean } = {}) {
const { copy, copied, ...rest } = useClipboard({
source,
legacy: true,
});
export function useCopy({ source, text = 'Copied to the clipboard' }: { source: Ref; text?: string }) {
const { copy } = useClipboard({ source });
const message = useMessage();
return {
async copy() {
await copy();
message.success(text);
...rest,
isJustCopied: copied,
async copy(content?: string, { notificationMessage }: { notificationMessage?: string } = {}) {
if (source) {
await copy();
}
else {
await copy(content);
}
if (createToast) {
message.success(notificationMessage ?? text);
}
},
};
}

View File

@@ -1,16 +1,35 @@
import { extension as getExtensionFromMime } from 'mime-types';
import type { Ref } from 'vue';
function getFileExtensionFromBase64({
base64String,
defaultExtension = 'txt',
}: {
base64String: string
defaultExtension?: string
}) {
const hasMimeType = base64String.match(/data:(.*?);base64/i);
if (hasMimeType) {
return getExtensionFromMime(hasMimeType[1]) || defaultExtension;
}
return defaultExtension;
}
export function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
return {
download() {
const base64 = source.value;
const mimeType = base64.match(/data:(.*?);base64/i)?.[1] ?? 'text/plain';
console.log({ mimeType });
const cleanFileName = filename ?? `file.${getExtensionFromMime(mimeType)}`;
const base64String = source.value;
if (base64String === '') {
throw new Error('Base64 string is empty');
}
const cleanFileName = filename ?? `file.${getFileExtensionFromBase64({ base64String })}`;
const a = document.createElement('a');
a.href = source.value;
a.href = base64String;
a.download = cleanFileName;
a.click();
},

View File

@@ -0,0 +1,30 @@
import { type MaybeRef, get } from '@vueuse/core';
import Fuse from 'fuse.js';
import { computed } from 'vue';
export { useFuzzySearch };
function useFuzzySearch<Data>({
search,
data,
options = {},
}: {
search: MaybeRef<string>
data: Data[]
options?: Fuse.IFuseOptions<Data> & { filterEmpty?: boolean }
}) {
const fuse = new Fuse(data, options);
const filterEmpty = options.filterEmpty ?? true;
const searchResult = computed<Data[]>(() => {
const query = get(search);
if (!filterEmpty && query === '') {
return data;
}
return fuse.search(query).map(({ item }) => item);
});
return { searchResult };
}

View File

@@ -0,0 +1,35 @@
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 unknown as T;
},
set(value) {
proxy.value = transformer.toQuery(value as never);
},
});
}

View File

@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest';
import { isFalsyOrHasThrown } from './validation';
describe('useValidation', () => {
describe('isFalsyOrHasThrown', () => {
it('should return true if the callback return nil, false or throw', () => {
expect(isFalsyOrHasThrown(() => false)).toBe(true);
expect(isFalsyOrHasThrown(() => null)).toBe(true);
expect(isFalsyOrHasThrown(() => undefined)).toBe(true);
expect(isFalsyOrHasThrown(() => {})).toBe(true);
expect(
isFalsyOrHasThrown(() => {
throw new Error('message');
}),
).toBe(true);
});
it('should return true for any truthy values and empty string and 0 values', () => {
expect(isFalsyOrHasThrown(() => true)).toBe(false);
expect(isFalsyOrHasThrown(() => 'string')).toBe(false);
expect(isFalsyOrHasThrown(() => 1)).toBe(false);
expect(isFalsyOrHasThrown(() => 0)).toBe(false);
expect(isFalsyOrHasThrown(() => '')).toBe(false);
expect(isFalsyOrHasThrown(() => [])).toBe(false);
expect(isFalsyOrHasThrown(() => ({}))).toBe(false);
});
});
});

View File

@@ -1,38 +1,77 @@
import { reactive, watch, type Ref } from 'vue';
import { type MaybeRef, get } from '@vueuse/core';
import _ from 'lodash';
import { type Ref, reactive, watch } from 'vue';
type UseValidationRule<T> = {
validator: (value: T) => boolean;
message: string;
};
type ValidatorReturnType = unknown;
function isFalsyOrHasThrown(cb: () => boolean) {
export interface UseValidationRule<T> {
validator: (value: T) => ValidatorReturnType
message: string
}
export function isFalsyOrHasThrown(cb: () => ValidatorReturnType): boolean {
try {
return !cb();
} catch (_) {
const returnValue = cb();
if (_.isNil(returnValue)) {
return true;
}
return returnValue === false;
}
catch (_) {
return true;
}
}
export function useValidation<T>({ source, rules }: { source: Ref<T>; rules: UseValidationRule<T>[] }) {
export interface ValidationAttrs {
feedback: string
validationStatus: string | undefined
}
export function useValidation<T>({
source,
rules,
watch: watchRefs = [],
}: {
source: Ref<T>
rules: MaybeRef<UseValidationRule<T>[]>
watch?: Ref<unknown>[]
}) {
const state = reactive<{
message: string;
status: undefined | 'error';
message: string
status: undefined | 'error'
isValid: boolean
attrs: ValidationAttrs
}>({
message: '',
status: undefined,
isValid: false,
attrs: {
validationStatus: undefined,
feedback: '',
},
});
watch([source], () => {
state.message = '';
state.status = undefined;
watch(
[source, ...watchRefs],
() => {
state.message = '';
state.status = undefined;
for (const rule of rules) {
if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
state.message = rule.message;
state.status = 'error';
for (const rule of get(rules)) {
if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
state.message = rule.message;
state.status = 'error';
}
}
}
});
state.isValid = state.status !== 'error';
state.attrs.feedback = state.message;
state.attrs.validationStatus = state.status;
},
{ immediate: true },
);
return state;
}

View File

@@ -23,12 +23,18 @@ export const config = figue({
env: {
doc: 'Application current env',
format: 'enum',
values: ['production', 'development', 'test'],
values: ['production', 'development', 'preview', 'test'],
default: 'development',
env: 'MODE',
env: 'VITE_VERCEL_ENV',
},
},
plausible: {
isTrackerEnabled: {
doc: 'Is the tracker enabled',
format: 'boolean',
default: false,
env: 'VITE_TRACKER_ENABLED',
},
domain: {
doc: 'Plausible current domain',
format: 'string',
@@ -47,13 +53,11 @@ export const config = figue({
default: false,
},
},
tools: {
newTools: {
doc: 'Tool names for tools flagged a as new',
format: 'array',
default: [],
env: 'VITE_NEW_TOOLS',
},
showBanner: {
doc: 'Show the banner',
format: 'boolean',
default: false,
env: 'VITE_SHOW_BANNER',
},
})
.loadEnv({

View File

@@ -1,160 +1,139 @@
<script lang="ts" setup>
import { NIcon, useThemeVars, type MenuGroupOption } from 'naive-ui';
import { h } from 'vue';
import { RouterLink, useRoute } from 'vue-router';
import { Heart, Menu2, Home2 } from '@vicons/tabler';
import { toolsByCategory } from '@/tools';
import SearchBar from '../components/SearchBar.vue';
import { useStyleStore } from '@/stores/style.store';
import { NIcon, useThemeVars } from 'naive-ui';
import { RouterLink } from 'vue-router';
import { Heart, Home2, Menu2 } from '@vicons/tabler';
import HeroGradient from '../assets/hero-gradient.svg?component';
import MenuLayout from '../components/MenuLayout.vue';
import NavbarButtons from '../components/NavbarButtons.vue';
import { toolsByCategory } from '@/tools';
import { useStyleStore } from '@/stores/style.store';
import { config } from '@/config';
import MenuIconItem from '@/components/MenuIconItem.vue';
import type { ITool } from '@/tools/tool';
import type { ToolCategory } from '@/tools/tools.types';
import { useToolStore } from '@/tools/tools.store';
import { useTracker } from '@/modules/tracker/tracker.services';
import CollapsibleToolMenu from '@/components/CollapsibleToolMenu.vue';
const themeVars = useThemeVars();
const route = useRoute();
const styleStore = useStyleStore();
const version = config.app.version;
const commitSha = config.app.lastCommitSha.slice(0, 7);
const makeLabel = (tool: ITool) => () => h(RouterLink, { to: tool.path }, { default: () => tool.name });
const makeIcon = (tool: ITool) => () => h(MenuIconItem, { tool });
const { tracker } = useTracker();
const menuOptions: MenuGroupOption[] = toolsByCategory.map((category) => ({
label: category.name,
key: category.name,
type: 'group',
children: category.components.map((tool) => ({
label: makeLabel(tool),
icon: makeIcon(tool),
key: tool.name,
})),
}));
const toolStore = useToolStore();
const tools = computed<ToolCategory[]>(() => [
...(toolStore.favoriteTools.length > 0 ? [{ name: 'Your favorite tools', components: toolStore.favoriteTools }] : []),
...toolsByCategory,
]);
</script>
<template>
<menu-layout class="menu-layout" :class="{ isSmallScreen: styleStore.isSmallScreen }">
<MenuLayout class="menu-layout" :class="{ isSmallScreen: styleStore.isSmallScreen }">
<template #sider>
<router-link to="/" class="hero-wrapper">
<hero-gradient class="gradient" />
<RouterLink to="/" class="hero-wrapper">
<HeroGradient class="gradient" />
<div class="text-wrapper">
<div class="title">IT - TOOLS</div>
<div class="title">
IT - TOOLS
</div>
<div class="divider" />
<div class="subtitle">Handy tools for developers</div>
<div class="subtitle">
Handy tools for developers
</div>
</div>
</router-link>
</RouterLink>
<div class="sider-content">
<n-space v-if="styleStore.isSmallScreen" justify="center">
<navbar-buttons />
</n-space>
<div v-if="styleStore.isSmallScreen" flex justify-center>
<NavbarButtons />
</div>
<n-menu
class="menu"
:value="(route.name as string)"
:collapsed-width="64"
:collapsed-icon-size="22"
:options="menuOptions"
:indent="20"
/>
<CollapsibleToolMenu :tools-by-category="tools" />
<div class="footer">
<div>
IT-Tools
<n-button
text
tag="a"
target="_blank"
rel="noopener"
type="primary"
depth="3"
:href="`https://github.com/CorentinTh/it-tools/tree/v${version}`"
>
<c-link target="_blank" rel="noopener" :href="`https://github.com/CorentinTh/it-tools/tree/v${version}`">
v{{ version }}
</n-button>
</c-link>
<template v-if="commitSha && commitSha.length > 0">
-
<n-button
text
tag="a"
<c-link
target="_blank"
rel="noopener"
type="primary"
depth="3"
:href="`https://github.com/CorentinTh/it-tools/tree/${commitSha}`"
>
{{ commitSha }}
</n-button>
</c-link>
</template>
</div>
<div>
© {{ new Date().getFullYear() }}
<n-button text tag="a" target="_blank" rel="noopener" type="primary" href="https://github.com/CorentinTh">
<c-link target="_blank" rel="noopener" href="https://github.com/CorentinTh">
Corentin Thomasset
</n-button>
</c-link>
</div>
</div>
</div>
</template>
<template #content>
<div class="navigation">
<n-button
:size="styleStore.isSmallScreen ? 'medium' : 'large'"
<div flex items-center justify-center gap-2>
<c-button
circle
quaternary
variant="text"
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 />
<NIcon size="25" :component="Menu2" />
</c-button>
<n-tooltip trigger="hover">
<template #trigger>
<n-button
type="primary"
tag="a"
href="https://github.com/sponsors/CorentinTh"
<c-button to="/" circle variant="text" aria-label="Home">
<NIcon size="25" :component="Home2" />
</c-button>
</template>
Home
</n-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>
<command-palette />
<div>
<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' })"
>
<n-icon v-if="!styleStore.isSmallScreen" :component="Heart" style="margin-right: 5px" />
Sponsor
</n-button>
Buy me a coffee
<NIcon v-if="!styleStore.isSmallScreen" :component="Heart" ml-2 />
</c-button>
</template>
Support IT Tools development !
</n-tooltip>
<navbar-buttons v-if="!styleStore.isSmallScreen" />
</div>
<slot />
</template>
</menu-layout>
</MenuLayout>
</template>
<style lang="less" scoped>
@@ -169,6 +148,19 @@ const menuOptions: MenuGroupOption[] = toolsByCategory.map((category) => ({
// background-size: @size @size;
// }
.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 !important;
transition: padding ease 0.2s !important;
&:hover {
color: #fff;
padding-left: 30px;
padding-right: 30px;
}
}
.footer {
text-align: center;
color: #838587;
@@ -219,25 +211,4 @@ const menuOptions: MenuGroupOption[] = toolsByCategory.map((category) => ({
}
}
}
// ::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>

View File

@@ -1,52 +1,53 @@
<script lang="ts" setup>
import { useRoute } from 'vue-router';
import BaseLayout from './base.layout.vue';
import { useHead } from '@vueuse/head';
import type { HeadObject } from '@vueuse/head';
import { computed } from 'vue';
import { useThemeVars } from 'naive-ui';
import BaseLayout from './base.layout.vue';
import FavoriteButton from '@/components/FavoriteButton.vue';
import type { Tool } from '@/tools/tools.types';
const route = useRoute();
const theme = useThemeVars();
const head = computed<HeadObject>(() => ({
title: `${route.meta.name} - IT Tools`,
meta: [
{
name: 'description',
content: route.meta.description,
content: route.meta?.description as string,
},
{
name: 'keywords',
content: route.meta.keywords,
content: ((route.meta.keywords ?? []) as string[]).join(','),
},
],
}));
useHead(head);
const { t } = useI18n();
const i18nKey = computed<string>(() => route.path.trim().replace('/', ''));
const toolTitle = computed<string>(() => t(`tools.${i18nKey.value}.title`, String(route.meta.name)));
const toolDescription = computed<string>(() => t(`tools.${i18nKey.value}.description`, String(route.meta.description)));
</script>
<template>
<base-layout>
<BaseLayout>
<div class="tool-layout">
<div class="tool-header">
<n-h1>
{{ route.meta.name }}
<div flex flex-nowrap items-center justify-between>
<n-h1>
{{ toolTitle }}
</n-h1>
<n-tag
v-if="route.meta.isNew"
round
type="success"
:bordered="false"
:color="{ color: theme.primaryColor, textColor: theme.tagColor }"
>
New tool
</n-tag>
<!-- <span class="new-tool-badge">New !</span> -->
</n-h1>
<div>
<FavoriteButton :tool="{ name: route.meta.name } as Tool" />
</div>
</div>
<div class="separator" />
<div class="description">
{{ route.meta.description }}
{{ toolDescription }}
</div>
</div>
</div>
@@ -54,7 +55,7 @@ useHead(head);
<div class="tool-content">
<slot />
</div>
</base-layout>
</BaseLayout>
</template>
<style lang="less" scoped>
@@ -92,6 +93,7 @@ useHead(head);
width: 200px;
height: 2px;
background: rgb(161, 161, 161);
opacity: 0.2;
margin: 10px 0;
}

View File

@@ -1,20 +1,25 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import { createHead } from '@vueuse/head';
import { registerSW } from 'virtual:pwa-register';
import { plausible } from './plugins/plausible.plugin';
registerSW();
import 'virtual:uno.css';
import { naive } from './plugins/naive.plugin';
import App from './App.vue';
import router from './router';
import { i18nPlugin } from './plugins/i18n.plugin';
registerSW();
const app = createApp(App);
app.use(createPinia());
app.use(createHead());
app.use(i18nPlugin);
app.use(router);
app.use(naive);
app.use(plausible);

View File

@@ -0,0 +1,82 @@
import { defineStore } from 'pinia';
import _ from 'lodash';
import type { PaletteOption } from './command-palette.types';
import { useToolStore } from '@/tools/tools.store';
import { useFuzzySearch } from '@/composable/fuzzySearch';
import { useStyleStore } from '@/stores/style.store';
import SunIcon from '~icons/mdi/white-balance-sunny';
import GithubIcon from '~icons/mdi/github';
import BugIcon from '~icons/mdi/bug-outline';
import DiceIcon from '~icons/mdi/dice-5';
export const useCommandPaletteStore = defineStore('command-palette', () => {
const toolStore = useToolStore();
const styleStore = useStyleStore();
const router = useRouter();
const searchPrompt = ref('');
const toolsOptions = toolStore.tools.map(tool => ({
...tool,
to: tool.path,
toolCategory: tool.category,
category: 'Tools',
}));
const searchOptions: PaletteOption[] = [
...toolsOptions,
{
name: 'Random tool',
description: 'Get a random tool from the list.',
action: () => {
const { path } = _.sample(toolStore.tools)!;
router.push(path);
},
icon: DiceIcon,
category: 'Tools',
keywords: ['random', 'tool', 'pick', 'choose', 'select'],
closeOnSelect: true,
},
{
name: 'Toggle dark mode',
description: 'Toggle dark mode on or off.',
action: () => styleStore.toggleDark(),
icon: SunIcon,
category: 'Actions',
keywords: ['dark', 'theme', 'toggle', 'mode', 'light', 'system'],
},
{
name: 'Github repository',
href: 'https://github.com/CorentinTh/it-tools',
category: 'External',
description: 'View the source code of it-tools on Github.',
keywords: ['github', 'repo', 'repository', 'source', 'code'],
icon: GithubIcon,
},
{
name: 'Report a bug or an issue',
description: 'Report a bug or an issue to help improve it-tools.',
href: 'https://github.com/CorentinTh/it-tools/issues/new/choose',
category: 'Actions',
keywords: ['report', 'issue', 'bug', 'problem', 'error'],
icon: BugIcon,
},
];
const { searchResult } = useFuzzySearch({
search: searchPrompt,
data: searchOptions,
options: {
keys: [{ name: 'name', weight: 2 }, 'description', 'keywords', 'category'],
threshold: 0.3,
},
});
const filteredSearchResult = computed(() =>
_.chain(searchResult.value).groupBy('category').mapValues(categoryOptions => _.take(categoryOptions, 5)).value());
return {
filteredSearchResult,
searchPrompt,
};
});

View File

@@ -0,0 +1,14 @@
import type { Component } from 'vue';
import type { RouteLocationRaw } from 'vue-router';
export interface PaletteOption {
name: string
description?: string
icon?: Component
action?: () => void
to?: RouteLocationRaw
category: string
keywords?: string[]
href?: string
closeOnSelect?: boolean
}

View File

@@ -0,0 +1,153 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import _ from 'lodash';
import { useCommandPaletteStore } from './command-palette.store';
import type { PaletteOption } from './command-palette.types';
const isModalOpen = ref(false);
const inputRef = ref();
const router = useRouter();
const isMac = computed(() => window.navigator.userAgent.toLowerCase().includes('mac'));
const commandPaletteStore = useCommandPaletteStore();
const { searchPrompt, filteredSearchResult } = storeToRefs(commandPaletteStore);
const keys = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.ctrlKey && e.key === 'k' && e.type === 'keydown') {
e.preventDefault();
}
if (e.metaKey && e.key === 'k' && e.type === 'keydown') {
e.preventDefault();
}
},
});
whenever(isModalOpen, () => inputRef.value?.focus());
whenever(keys.ctrl_k, open);
whenever(keys.meta_k, open);
whenever(keys.escape, close);
function open() {
return isModalOpen.value = true;
}
function close() {
isModalOpen.value = false;
}
const selectedOptionIndex = ref(0);
function handleKeydown(event: KeyboardEvent) {
const { key } = event;
const isEnterPressed = key === 'Enter';
const isArrowUpOrDown = ['ArrowUp', 'ArrowDown'].includes(key);
const isArrowDown = key === 'ArrowDown';
if (isArrowUpOrDown) {
const increment = isArrowDown ? 1 : -1;
const maxIndex = Math.max(_.chain(filteredSearchResult.value).values().flatten().size().value() - 1, 0);
selectedOptionIndex.value = Math.min(Math.max(selectedOptionIndex.value + increment, 0), maxIndex);
return;
}
if (isEnterPressed) {
const option = _.chain(filteredSearchResult.value)
.values()
.flatten()
.nth(selectedOptionIndex.value)
.value();
activateOption(option);
}
}
function getOptionIndex(option: PaletteOption) {
return _.chain(filteredSearchResult.value)
.values()
.flatten()
.findIndex(o => o === option)
.value();
}
function activateOption(option: PaletteOption) {
const { closeOnSelect } = option;
if (option.action) {
option.action();
if (closeOnSelect) {
close();
}
return;
}
const closeAfterNavigation = closeOnSelect || _.isUndefined(closeOnSelect);
if (option.to) {
router.push(option.to);
if (closeAfterNavigation) {
close();
}
return;
}
if (option.href) {
window.open(option.href, '_blank');
if (closeAfterNavigation) {
close();
}
}
}
</script>
<template>
<div flex-1>
<c-button w-full important:justify-start @click="isModalOpen = true">
<span flex items-center gap-3 op-40>
<icon-mdi-search />
Search...
<span hidden flex-1 border border-current border-op-40 rounded border-solid px-5px py-3px sm:inline>
{{ isMac ? 'Cmd' : 'Ctrl' }}&nbsp;+&nbsp;K
</span>
</span>
</c-button>
<c-modal v-model:open="isModalOpen" class="palette-modal" shadow-xl important:max-w-650px important:pa-12px @keydown="handleKeydown">
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
<div v-for="(options, category) in filteredSearchResult" :key="category">
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
{{ category }}
</div>
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
</div>
</c-modal>
</div>
</template>
<style scoped lang="less">
.c-input-text {
font-size: 18px;
::v-deep(.input-wrapper) {
padding: 4px;
padding-left: 18px;
}
}
.c-modal--overlay {
align-items: flex-start !important;
padding-top: 80px;
}
</style>

View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import type { PaletteOption } from '../command-palette.types';
const props = withDefaults(defineProps<{ option: PaletteOption; selected?: boolean }>(), {
selected: false,
});
const emit = defineEmits(['activated']);
const { option } = toRefs(props);
const { selected } = toRefs(props);
</script>
<template>
<div
role="option"
:aria-selected="selected"
:class="{
'text-white': selected,
'bg-primary': selected,
}"
w-full flex cursor-pointer items-center overflow-hidden rounded pa-3 transition hover:bg-primary hover:text-white
@click="() => emit('activated', option)"
>
<component :is="option.icon" v-if="option.icon" mr-3 h-30px w-30px shrink-0 op-50 />
<div flex-1 overflow-hidden>
<div truncate font-bold lh-tight op-90>
{{ option.name }}
</div>
<div v-if="option.description" truncate lh-tight op-60>
{{ option.description }}
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,7 @@
import { format } from 'date-fns';
export { getUrlFriendlyDateTime };
function getUrlFriendlyDateTime({ date = new Date() }: { date?: Date } = {}) {
return format(date, 'yyyy-MM-dd-HH-mm-ss');
}

View File

@@ -0,0 +1,5 @@
function clamp({ value, min = 0, max = 100 }: { value: number; min?: number; max?: number }) {
return Math.min(Math.max(value, min), max);
}
export { clamp };

View File

@@ -0,0 +1,27 @@
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 TypeError('Plausible must be instantiated');
}
const tracker = createTrackerService({ plausible });
return {
tracker,
};
}

View File

@@ -0,0 +1,3 @@
import type { createTrackerService } from './tracker.services';
export type TrackerService = ReturnType<typeof createTrackerService>;

View File

@@ -5,19 +5,23 @@ useHead({ title: 'Page not found - IT Tools' });
</script>
<template>
<div class="e404-wrapper">
<n-result status="404" title="404 Not Found" description="Sorry, this page does not seem to exist">
<template #footer>
<router-link to="/" #="{ navigate, href }" custom>
<n-button tag="a" :href="href" secondary type="success" @click="navigate"> Back home </n-button>
</router-link>
</template>
</n-result>
<div mt-20 flex flex-col items-center>
<span text-90px lh-1 op-50>
<icon-mdi:kettle-steam-outline />
</span>
<h1 m-0 mt-3>
404 Not Found
</h1>
<div mt-4 op-60>
Sorry, this page does not seem to exist
</div>
<div mb-8 op-60>
Maybe the cache is doing tricky things, try force-refreshing?
</div>
<c-button to="/">
Back home
</c-button>
</div>
</template>
<style scoped>
.e404-wrapper {
padding-top: 150px;
}
</style>

View File

@@ -1,7 +1,9 @@
<script setup lang="ts">
import { useHead } from '@vueuse/head';
import { useTracker } from '@/modules/tracker/tracker.services';
useHead({ title: 'About - IT Tools' });
const { tracker } = useTracker();
</script>
<template>
@@ -9,73 +11,59 @@ useHead({ title: 'About - IT Tools' });
<n-h1>About</n-h1>
<n-p>
This wonderful website, made with by
<n-button text tag="a" href="https://github.com/CorentinTh" target="_blank" rel="noopener" type="primary">
Corentin Thomasset </n-button
>, aggregates useful tools for developer and people working in IT. If you find it useful, please fell free to
share it to people you think may find it useful too and don't forget to pin it in your shortcut bar !
<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 cost 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
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
<n-button
type="primary"
tag="a"
text
href="https://github.com/sponsors/CorentinTh"
<c-link
href="https://www.buymeacoffee.com/cthmsst"
rel="noopener"
target="_blank"
@click="() => tracker.trackEvent({ eventName: 'Support button clicked' })"
>
sponsoring me </n-button
>.
sponsoring me
</c-link>.
</n-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
<n-button
type="primary"
tag="a"
text
href="https://github.com/CorentinTh/it-tools/blob/master/package.json"
rel="noopener"
target="_blank"
>
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
<c-link href="https://github.com/CorentinTh/it-tools/blob/main/package.json" rel="noopener" target="_blank">
package.json
</n-button>
</c-link>
file of the repository.
</n-p>
<n-h2>Found a bug ? A tool is missing ?</n-h2>
<n-h2>Found a bug? A tool is missing?</n-h2>
<n-p>
If you need a tool that is currently not present here, and you think can be relevant, you are welcome to submit a
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
<n-button
type="primary"
tag="a"
text
href="https://github.com/CorentinTh/it-tools/issues/new?assignees=CorentinTh&labels=enhancement&template=feature_request.md&title=%5BFEAT%5D%20My%20feature"
<c-link
href="https://github.com/CorentinTh/it-tools/issues/new/choose"
rel="noopener"
target="_blank"
>
issues section
</n-button>
in the github repository.
</c-link>
in the GitHub repository.
</n-p>
<n-p>
And if you found a bug, or something broken that doesn't work as expected, please fill a bug report in the
<n-button
type="primary"
tag="a"
text
href="https://github.com/CorentinTh/it-tools/issues/new?assignees=CorentinTh&labels=bug&template=bug_report.md&title=%5BBUG%5D%20My%20bug"
And if you found a bug, or something doesn't work as expected, please file a bug report in the
<c-link
href="https://github.com/CorentinTh/it-tools/issues/new/choose"
rel="noopener"
target="_blank"
>
issues section
</n-button>
in the github repository.
</c-link>
in the GitHub repository.
</n-p>
</div>
</template>

View File

@@ -1,24 +1,71 @@
<script setup lang="ts">
import { toolsWithCategory } from '@/tools';
import ToolCard from '../components/ToolCard.vue';
import { Heart } from '@vicons/tabler';
import { useHead } from '@vueuse/head';
import ColoredCard from '../components/ColoredCard.vue';
import ToolCard from '../components/ToolCard.vue';
import { useToolStore } from '@/tools/tools.store';
import { config } from '@/config';
const toolStore = useToolStore();
useHead({ title: 'IT Tools - Handy online tools for developers' });
const { t } = useI18n();
</script>
<template>
<div class="home-page">
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
<n-gi
v-for="tool in [
...toolsWithCategory.filter(({ isNew }) => isNew),
...toolsWithCategory.filter(({ isNew }) => !isNew),
]"
:key="tool.name"
>
<tool-card :tool="tool" />
</n-gi>
</n-grid>
<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
<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" />
</ColoredCard>
</n-gi>
</n-grid>
<transition name="height">
<div v-if="toolStore.favoriteTools.length > 0">
<n-h3>Your favorite tools</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" />
</n-gi>
</n-grid>
</div>
</transition>
<div v-if="toolStore.newTools.length > 0">
<n-h3>{{ t('home.categories.newestTools', 'Newest tools') }}</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.newTools" :key="tool.name">
<ToolCard :tool="tool" />
</n-gi>
</n-grid>
</div>
<n-h3>All the tools</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>
<ToolCard :tool="tool" />
</transition>
</n-gi>
</n-grid>
</div>
</div>
</template>
@@ -26,4 +73,27 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
.home-page {
padding-top: 50px;
}
.n-h3 {
margin-bottom: 10px;
}
::v-deep(.n-grid) {
margin-bottom: 30px;
}
.height-enter-active,
.height-leave-active {
transition: all 0.5s ease-in-out;
overflow: hidden;
max-height: 500px;
}
.height-enter-from,
.height-leave-to {
max-height: 42px;
overflow: hidden;
opacity: 0;
margin-bottom: 0;
}
</style>

View File

@@ -0,0 +1,15 @@
import type { App } from 'vue';
import { createI18n } from 'vue-i18n';
import messages from '@intlify/unplugin-vue-i18n/messages';
const i18n = createI18n({
legacy: false,
locale: 'en',
messages,
});
export const i18nPlugin = {
install: (app: App) => {
app.use(i18n);
},
};

View File

@@ -1,112 +1,3 @@
import {
create,
NButton,
NConfigProvider,
NCard,
NInput,
NColorPicker,
NInputNumber,
NSpace,
NH1,
NForm,
NFormItem,
NTimePicker,
NText,
NIcon,
NSwitch,
NCollapseTransition,
NGrid,
NGridItem,
NPopconfirm,
NSlider,
NCollapse,
NCollapseItem,
NProgress,
NAutoComplete,
NSelect,
NUpload,
NEmpty,
NModal,
NTooltip,
NAlert,
NP,
NH2,
NDropdown,
NLayout,
NLayoutSider,
NMenu,
NMessageProvider,
NPageHeader,
NResult,
NH3,
NEllipsis,
NTag,
NInputGroup,
NInputGroupLabel,
NDivider,
NStatistic,
NTable,
NUploadDragger,
NImage,
NScrollbar,
NGradientText,
NCode,
NDatePicker,
} from 'naive-ui';
import { create } from 'naive-ui';
const components = [
NDatePicker,
NCode,
NGradientText,
NScrollbar,
NImage,
NUploadDragger,
NTable,
NStatistic,
NDivider,
NInputGroup,
NInputGroupLabel,
NTag,
NResult,
NEllipsis,
NPageHeader,
NMessageProvider,
NLayout,
NLayoutSider,
NMenu,
NDropdown,
NH2,
NH3,
NP,
NAlert,
NTooltip,
NModal,
NEmpty,
NUpload,
NSelect,
NAutoComplete,
NProgress,
NCollapse,
NCollapseItem,
NSlider,
NPopconfirm,
NGrid,
NGridItem,
NButton,
NConfigProvider,
NCard,
NInput,
NColorPicker,
NInputNumber,
NSpace,
NH1,
NForm,
NFormItem,
NTimePicker,
NText,
NIcon,
NSwitch,
NCollapseTransition,
];
export const naive = create({ components });
export const naive = create();

View File

@@ -1,12 +1,38 @@
import { config } from '@/config';
import { noop } from 'lodash';
import Plausible from 'plausible-tracker';
import type { App } from 'vue';
import { config } from '@/config';
function createFakePlausibleInstance(): Pick<ReturnType<typeof Plausible>, 'trackEvent' | 'enableAutoPageviews'> {
return {
trackEvent: noop,
enableAutoPageviews: () => noop,
};
}
function createPlausibleInstance({
config,
}: {
config: {
isTrackerEnabled: boolean
domain: string
apiHost: string
trackLocalhost: boolean
}
}) {
if (config.isTrackerEnabled) {
return Plausible(config);
}
return createFakePlausibleInstance();
}
export const plausible = {
install: (app: App) => {
const plausible = Plausible(config.plausible);
const plausible = createPlausibleInstance({ config: config.plausible });
plausible.enableAutoPageviews();
app.config.globalProperties.$plausible = plausible;
app.provide('plausible', plausible);
},
};

View File

@@ -1,9 +1,10 @@
import { layouts } from './layouts/index';
import { createRouter, createWebHistory } from 'vue-router';
import { layouts } from './layouts/index';
import HomePage from './pages/Home.page.vue';
import NotFound from './pages/404.page.vue';
import { tools } from './tools';
import { config } from './config';
import { routes as demoRoutes } from './ui/demo/demo.routes';
const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({
path,
@@ -14,7 +15,7 @@ const toolsRoutes = tools.map(({ path, name, component, ...config }) => ({
const toolsRedirectRoutes = tools
.filter(({ redirectFrom }) => redirectFrom && redirectFrom.length > 0)
.flatMap(
({ path, redirectFrom }) => redirectFrom?.map((redirectSource) => ({ path: redirectSource, redirect: path })) ?? [],
({ path, redirectFrom }) => redirectFrom?.map(redirectSource => ({ path: redirectSource, redirect: path })) ?? [],
);
const router = createRouter({
@@ -32,6 +33,7 @@ const router = createRouter({
},
...toolsRoutes,
...toolsRedirectRoutes,
...(config.app.env === 'development' ? demoRoutes : []),
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
],
});

27
src/shims.d.ts vendored
View File

@@ -1,10 +1,35 @@
declare module '*.vue' {
import type { ComponentOptions, ComponentOptions } from 'vue';
import type { ComponentOptions } from 'vue';
const Component: ComponentOptions;
export default Component;
}
declare module '*.md' {
import type { ComponentOptions } from 'vue';
const Component: ComponentOptions;
export default Component;
}
declare module 'iarna-toml-esm' {
export const parse: (toml: string) => any;
export const stringify: (obj: any) => string;
}
declare module 'emojilib' {
const lib: Record<string, string[]>;
export default lib;
}
declare module 'unicode-emoji-json' {
const emoji: Record<string, {
name: string;
slug: string;
group: string;
emoji_version: string;
unicode_version: string;
skin_tone_support: boolean;
skin_tone_support_unicode_version: string;
}>;
export default emoji;
}

View File

@@ -1,17 +1,19 @@
import { useMediaQuery, useStorage } from '@vueuse/core';
import { useDark, useMediaQuery, useStorage, useToggle } from '@vueuse/core';
import { defineStore } from 'pinia';
import { watch, type Ref } from 'vue';
import { type Ref, watch } from 'vue';
export const useStyleStore = defineStore('style', {
state: () => {
const isDarkTheme = useStorage('isDarkTheme', true) as Ref<boolean>;
const isDarkTheme = useDark();
const toggleDark = useToggle(isDarkTheme);
const isSmallScreen = useMediaQuery('(max-width: 700px)');
const isMenuCollapsed = useStorage('isMenuCollapsed', isSmallScreen.value) as Ref<boolean>;
watch(isSmallScreen, (v) => (isMenuCollapsed.value = v));
watch(isSmallScreen, v => (isMenuCollapsed.value = v));
return {
isDarkTheme,
toggleDark,
isMenuCollapsed,
isSmallScreen,
};

View File

@@ -6,6 +6,12 @@ export const lightThemeOverrides: GlobalThemeOverrides = {
},
Layout: { color: '#f1f5f9' },
AutoComplete: {
peers: {
InternalSelectMenu: { height: '500px' },
},
},
};
export const darkThemeOverrides: GlobalThemeOverrides = {
@@ -16,18 +22,33 @@ export const darkThemeOverrides: GlobalThemeOverrides = {
primaryColorSuppl: '#36AD6AFF',
},
Notification: {
color: '#333333',
},
AutoComplete: {
peers: {
InternalSelectMenu: { height: '500px', color: '#1e1e1e' },
},
},
Menu: {
itemHeight: '32px',
},
Layout: {
color: '#121212',
siderColor: '#1e1e1e',
color: '#1c1c1c',
siderColor: '#232323',
siderBorderColor: 'transparent',
},
Card: {
color: '#1e1e1e',
borderColor: 'transparent',
color: '#232323',
borderColor: '#282828',
},
Table: {
tdColor: '#232323',
thColor: '#353535',
},
};

View File

@@ -1,38 +1,37 @@
<template>
<n-card title="Base64 to file">
<n-input v-model:value="base64Input" type="textarea" placeholder="Put your base64 file string here..." rows="5" />
<n-space justify="center">
<n-button secondary @click="download()"> Download file </n-button>
</n-space>
</n-card>
<n-card title="File to base64">
<n-upload v-model:file-list="fileList" :show-file-list="true" :on-before-upload="onUpload" list-type="image">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="35" :depth="3" :component="Upload" />
</div>
<n-text style="font-size: 14px"> Click or drag a file to this area to upload </n-text>
</n-upload-dragger>
</n-upload>
<n-input :value="fileBase64" type="textarea" readonly placeholder="File in ase64 will be here" />
<n-space justify="center">
<n-button secondary @click="copyFileBase64()"> Copy </n-button>
</n-space>
</n-card>
</template>
<script setup lang="ts">
import { useCopy } from '@/composable/copy';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
import { Upload } from '@vicons/tabler';
import { useBase64 } from '@vueuse/core';
import type { UploadFileInfo } from 'naive-ui';
import { ref, type Ref } from 'vue';
import type { Ref } from 'vue';
import { useCopy } from '@/composable/copy';
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
import { useValidation } from '@/composable/validation';
import { isValidBase64 } from '@/utils/base64';
const base64Input = ref('');
const { download } = useDownloadFileFromBase64({ source: base64Input });
const base64InputValidation = useValidation({
source: base64Input,
rules: [
{
message: 'Invalid base 64 string',
validator: value => isValidBase64(value.trim()),
},
],
});
function downloadFile() {
if (!base64InputValidation.isValid) {
return;
}
try {
download();
}
catch (_) {
//
}
}
const fileList = ref();
const fileInput = ref() as Ref<File>;
@@ -47,12 +46,47 @@ async function onUpload({ file: { file } }: { file: UploadFileInfo }) {
}
</script>
<style lang="less" scoped>
.n-input,
.n-upload {
margin-bottom: 15px;
}
<template>
<c-card title="Base64 to file">
<c-input-text
v-model:value="base64Input"
multiline
placeholder="Put your base64 file string here..."
rows="5"
:validation="base64InputValidation"
mb-2
/>
<div flex justify-center>
<c-button :disabled="base64Input === '' || !base64InputValidation.isValid" @click="downloadFile()">
Download file
</c-button>
</div>
</c-card>
<c-card title="File to base64">
<n-upload v-model:file-list="fileList" :show-file-list="true" :on-before-upload="onUpload" list-type="image">
<n-upload-dragger>
<div mb-2>
<n-icon size="35" :depth="3" :component="Upload" />
</div>
<div op-60>
Click or drag a file to this area to upload
</div>
</n-upload-dragger>
</n-upload>
<c-input-text :value="fileBase64" multiline readonly placeholder="File in base64 will be here" rows="5" mb-2 />
<div flex justify-center>
<c-button @click="copyFileBase64()">
Copy
</c-button>
</div>
</c-card>
</template>
<style lang="less" scoped>
::v-deep(.n-upload-trigger) {
width: 100%;
}

View File

@@ -3,10 +3,9 @@ import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Base64 file converter',
path: '/base64-converter',
description: "Convert string, files or images into a it's base64 representation.",
path: '/base64-file-converter',
description: 'Convert string, files or images into a it\'s base64 representation.',
keywords: ['base64', 'converter', 'upload', 'image', 'file', 'conversion', 'web', 'data', 'format'],
component: () => import('./base64-file-converter.vue'),
icon: FileDigit,
redirectFrom: ['/file-to-base64', '/base64-string-converter'],
});

View File

@@ -1,65 +1,90 @@
<template>
<n-card title="String to base64">
<n-form-item label="String to encode">
<n-input v-model:value="textInput" type="textarea" placeholder="Put your string here..." rows="5" />
</n-form-item>
<n-form-item label="Base64 of string">
<n-input
:value="base64Output"
type="textarea"
readonly
placeholder="The base64 encoding of your string will be here"
rows="5"
/>
</n-form-item>
<n-space justify="center">
<n-button secondary @click="copyTextBase64()"> Copy base64 </n-button>
</n-space>
</n-card>
<n-card title="Base64 to string">
<n-form-item
label="Base64 string to decode"
:validation-status="b64Validation.status"
:feedback="b64Validation.message"
>
<n-input v-model:value="base64Input" type="textarea" placeholder="Your base64 string..." rows="5" />
</n-form-item>
<n-form-item label="Decoded string">
<n-input :value="textOutput" type="textarea" readonly placeholder="The decoded string will be here" rows="5" />
</n-form-item>
<n-space justify="center">
<n-button secondary @click="copyText()"> Copy decoded string </n-button>
</n-space>
</n-card>
</template>
<script setup lang="ts">
import { useCopy } from '@/composable/copy';
import { useValidation } from '@/composable/validation';
import { computed, ref } from 'vue';
import { base64ToText, isValidBase64, textToBase64 } from '@/utils/base64';
import { withDefaultOnError } from '@/utils/defaults';
const encodeUrlSafe = useStorage('base64-string-converter--encode-url-safe', false);
const decodeUrlSafe = useStorage('base64-string-converter--decode-url-safe', false);
const textInput = ref('');
const base64Output = computed(() => window.btoa(textInput.value));
const base64Output = computed(() => textToBase64(textInput.value, { makeUrlSafe: encodeUrlSafe.value }));
const { copy: copyTextBase64 } = useCopy({ source: base64Output, text: 'Base64 string copied to the clipboard' });
const base64Input = ref('');
const textOutput = computed(() => {
try {
return window.atob(base64Input.value);
} catch (_) {
return '';
}
});
const textOutput = computed(() =>
withDefaultOnError(() => base64ToText(base64Input.value.trim(), { makeUrlSafe: decodeUrlSafe.value }), ''),
);
const { copy: copyText } = useCopy({ source: textOutput, text: 'String copied to the clipboard' });
const b64Validation = useValidation({
source: base64Input,
rules: [{ message: 'Invalid base64 string', validator: (value) => true || window.atob(value) }],
});
const b64ValidationRules = [
{
message: 'Invalid base64 string',
validator: (value: string) => isValidBase64(value.trim(), { makeUrlSafe: decodeUrlSafe.value }),
},
];
const b64ValidationWatch = [decodeUrlSafe];
</script>
<style lang="less" scoped></style>
<template>
<c-card title="String to base64">
<n-form-item label="Encode URL safe" label-placement="left">
<n-switch v-model:value="encodeUrlSafe" />
</n-form-item>
<c-input-text
v-model:value="textInput"
multiline
placeholder="Put your string here..."
rows="5"
label="String to encode"
raw-text
mb-5
/>
<c-input-text
label="Base64 of string"
:value="base64Output"
multiline
readonly
placeholder="The base64 encoding of your string will be here"
rows="5"
mb-5
/>
<div flex justify-center>
<c-button @click="copyTextBase64()">
Copy base64
</c-button>
</div>
</c-card>
<c-card title="Base64 to string">
<n-form-item label="Decode URL safe" label-placement="left">
<n-switch v-model:value="decodeUrlSafe" />
</n-form-item>
<c-input-text
v-model:value="base64Input"
multiline
placeholder="Your base64 string..."
rows="5"
:validation-rules="b64ValidationRules"
:validation-watch="b64ValidationWatch"
label="Base64 string to decode"
mb-5
/>
<c-input-text
v-model:value="textOutput"
label="Decoded string"
placeholder="The decoded string will be here"
multiline
rows="5"
readonly
mb-5
/>
<div flex justify-center>
<c-button @click="copyText()">
Copy decoded string
</c-button>
</div>
</c-card>
</template>

View File

@@ -1,44 +1,42 @@
<script setup lang="ts">
import { useCopy } from '@/composable/copy';
import { textToBase64 } from '@/utils/base64';
const username = ref('');
const password = ref('');
const header = computed(() => `Authorization: Basic ${textToBase64(`${username.value}:${password.value}`)}`);
const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard' });
</script>
<template>
<div>
<n-form-item label="Username">
<n-input v-model:value="username" placeholder="Your username..." clearable />
</n-form-item>
<n-form-item label="Password">
<n-input
v-model:value="password"
placeholder="Your password..."
type="password"
show-password-on="click"
clearable
/>
</n-form-item>
<c-input-text v-model:value="username" label="Username" placeholder="Your username..." clearable raw-text mb-5 />
<c-input-text
v-model:value="password"
label="Password"
placeholder="Your password..."
clearable
raw-text
mb-2
type="password"
/>
<br />
<n-card>
<c-card>
<n-statistic label="Authorization header:" class="header">
<n-scrollbar x-scrollable style="max-width: 550px; margin-bottom: -10px; padding-bottom: 10px" trigger="none">
{{ header }}
</n-scrollbar>
</n-statistic>
</n-card>
<br />
<n-space justify="center">
<n-button secondary @click="copy">Copy header</n-button>
</n-space>
</c-card>
<div mt-5 flex justify-center>
<c-button @click="copy()">
Copy header
</c-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useCopy } from '@/composable/copy';
import { computed, ref } from 'vue';
const username = ref('');
const password = ref('');
const header = computed(() => `Authorization: Basic ${window.btoa(`${username.value}:${password.value}`)}`);
const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard' });
</script>
<style lang="less" scoped>
::v-deep(.n-statistic-value__content) {
font-family: monospace;

View File

@@ -1,63 +1,7 @@
<template>
<n-card title="Hash">
<n-form label-width="120">
<n-form-item label="Your string: " label-placement="left">
<n-input
v-model:value="input"
placeholder="Your string to bcrypt..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<n-form-item label="Salt count: " label-placement="left">
<n-input-number v-model:value="saltCount" placeholder="Salt rounds..." :max="10" :min="0" style="width: 100%" />
</n-form-item>
<n-input :value="hashed" readonly style="text-align: center" />
</n-form>
<br />
<n-space justify="center">
<n-button secondary @click="copy"> Copy hash </n-button>
</n-space>
</n-card>
<n-card title="Compare string with hash">
<n-form label-width="120">
<n-form-item label="Your string: " label-placement="left">
<n-input
v-model:value="compareString"
placeholder="Your string to compare..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<n-form-item label="Your hash: " label-placement="left">
<n-input
v-model:value="compareHash"
placeholder="Your hahs to compare..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
</n-form-item>
<n-form-item label="Do they match ? " label-placement="left" :show-feedback="false">
<div class="compare-result" :class="{ positive: compareMatch }">
{{ compareMatch ? 'Yes' : 'No' }}
</div>
</n-form-item>
</n-form>
</n-card>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { hashSync, compareSync } from 'bcryptjs';
import { useCopy } from '@/composable/copy';
import { compareSync, hashSync } from 'bcryptjs';
import { useThemeVars } from 'naive-ui';
import { useCopy } from '@/composable/copy';
const themeVars = useThemeVars();
@@ -71,6 +15,47 @@ const compareHash = ref('');
const compareMatch = computed(() => compareSync(compareString.value, compareHash.value));
</script>
<template>
<c-card title="Hash">
<c-input-text
v-model:value="input"
placeholder="Your string to bcrypt..."
raw-text
label="Your string: "
label-position="left"
label-width="120px"
mb-2
/>
<n-form-item label="Salt count: " label-placement="left" label-width="120">
<n-input-number v-model:value="saltCount" placeholder="Salt rounds..." :max="10" :min="0" w-full />
</n-form-item>
<c-input-text :value="hashed" readonly text-center />
<div mt-5 flex justify-center>
<c-button @click="copy()">
Copy hash
</c-button>
</div>
</c-card>
<c-card title="Compare string with hash">
<n-form label-width="120">
<n-form-item label="Your string: " label-placement="left">
<c-input-text v-model:value="compareString" placeholder="Your string to compare..." raw-text />
</n-form-item>
<n-form-item label="Your hash: " label-placement="left">
<c-input-text v-model:value="compareHash" placeholder="Your hahs to compare..." raw-text />
</n-form-item>
<n-form-item label="Do they match ? " label-placement="left" :show-feedback="false">
<div class="compare-result" :class="{ positive: compareMatch }">
{{ compareMatch ? 'Yes' : 'No' }}
</div>
</n-form-item>
</n-form>
</c-card>
</template>
<style lang="less" scoped>
.compare-result {
color: v-bind('themeVars.errorColor');

View File

@@ -0,0 +1,34 @@
import _ from 'lodash';
export { computeAverage, computeVariance, arrayToMarkdownTable };
function computeAverage({ data }: { data: number[] }) {
if (data.length === 0) {
return 0;
}
return _.sum(data) / data.length;
}
function computeVariance({ data }: { data: number[] }) {
const mean = computeAverage({ data });
const squaredDiffs = data.map(value => (value - mean) ** 2);
return computeAverage({ data: squaredDiffs });
}
function arrayToMarkdownTable({ data, headerMap = {} }: { data: Record<string, unknown>[]; headerMap?: Record<string, string> }) {
if (!Array.isArray(data) || data.length === 0) {
return '';
}
const headers = Object.keys(data[0]);
const rows = data.map(obj => Object.values(obj));
const headerRow = `| ${headers.map(header => headerMap[header] ?? header).join(' | ')} |`;
const separatorRow = `| ${headers.map(() => '---').join(' | ')} |`;
const dataRows = rows.map(row => `| ${row.join(' | ')} |`).join('\n');
return `${headerRow}\n${separatorRow}\n${dataRows}`;
}

View File

@@ -0,0 +1,164 @@
<script setup lang="ts">
import { Plus, Trash } from '@vicons/tabler';
import { useStorage } from '@vueuse/core';
import _ from 'lodash';
import { arrayToMarkdownTable, computeAverage, computeVariance } from './benchmark-builder.models';
import DynamicValues from './dynamic-values.vue';
import { useCopy } from '@/composable/copy';
const suites = useStorage('benchmark-builder:suites', [
{ title: 'Suite 1', data: [5, 10] },
{ title: 'Suite 2', data: [8, 12] },
]);
const unit = useStorage('benchmark-builder:unit', '');
const round = (v: number) => Math.round(v * 1000) / 1000;
const results = computed(() => {
return suites.value
.map(({ data: dirtyData, title }) => {
const data = dirtyData.filter(_.isNumber);
return {
title,
size: data.length,
mean: computeAverage({ data }),
variance: computeVariance({ data }),
};
})
.sort((a, b) => a.mean - b.mean)
.map(({ mean, variance, size, title }, index, suites) => {
const cleanUnit = unit.value.trim();
const bestMean: number = suites[0].mean;
const deltaWithBestMean = mean - bestMean;
const ratioWithBestMean = bestMean === 0 ? '∞' : round(mean / bestMean);
const comparisonValues: string
= (index !== 0 && bestMean !== mean) ? ` (+${round(deltaWithBestMean)}${cleanUnit} ; x${ratioWithBestMean})` : '';
return {
position: index + 1,
title,
mean: `${round(mean)}${cleanUnit}${comparisonValues}`,
variance: `${round(variance)}${cleanUnit}${cleanUnit ? '²' : ''}`,
size,
};
});
});
const { copy } = useCopy({ createToast: false });
const header = {
title: 'Suite',
size: 'Samples',
mean: 'Mean',
variance: 'Variance',
position: 'Position',
};
function copyAsMarkdown() {
copy(arrayToMarkdownTable({ data: results.value, headerMap: header }));
}
function copyAsBulletList() {
const bulletList = results.value
.flatMap(({ title, ...sections }) => {
return [
` - ${title}`,
...Object.entries(sections).map(
([key, value]) => ` - ${header[key as keyof typeof header] ?? key}: ${value}`,
),
];
})
.join('\n');
copy(bulletList);
}
</script>
<template>
<n-scrollbar style="flex: 1" x-scrollable>
<div mb-5 flex flex-1 flex-nowrap justify-center gap-12px>
<div v-for="(suite, index) of suites" :key="index">
<c-card style="width: 294px">
<c-input-text
v-model:value="suite.title"
label-position="left"
label="Suite name"
placeholder="Suite name..."
clearable
/>
<n-divider />
<n-form-item label="Suite values" :show-feedback="false">
<DynamicValues v-model:values="suite.data" />
</n-form-item>
</c-card>
<div flex justify-center>
<c-button v-if="suites.length > 1" variant="text" @click="suites.splice(index, 1)">
<n-icon :component="Trash" depth="3" mr-2 size="18" />
Delete suite
</c-button>
<c-button
variant="text"
@click="suites.splice(index + 1, 0, { data: [0], title: `Suite ${suites.length + 1}` })"
>
<n-icon :component="Plus" depth="3" mr-2 size="18" />
Add suite
</c-button>
</div>
</div>
</div>
</n-scrollbar>
<div style="flex: 0 0 100%">
<div style="max-width: 600px; margin: 0 auto">
<div mx-auto max-w-sm flex justify-center gap-3>
<c-input-text v-model:value="unit" placeholder="Unit (eg: ms)" label="Unit" label-position="left" mb-4 />
<c-button
@click="
suites = [
{ title: 'Suite 1', data: [] },
{ title: 'Suite 2', data: [] },
]
"
>
Reset suites
</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>
<div mt-5 flex justify-center gap-3>
<c-button @click="copyAsMarkdown()">
Copy as markdown table
</c-button>
<c-button @click="copyAsBulletList()">
Copy as bullet list
</c-button>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,57 @@
<script setup lang="ts">
import { Plus, Trash } from '@vicons/tabler';
import { useTemplateRefsList, useVModel } from '@vueuse/core';
import { NInputNumber } from 'naive-ui';
import { nextTick } from 'vue';
const props = defineProps<{ values: (number | null)[] }>();
const emit = defineEmits(['update:values']);
const refs = useTemplateRefsList<typeof NInputNumber>();
const values = useVModel(props, 'values', emit);
async function addValue() {
values.value.push(null);
await nextTick();
refs.value.at(-1)?.focus();
}
function onInputEnter(index: number) {
if (index === values.value.length - 1) {
addValue();
return;
}
refs.value.at(index + 1)?.focus();
}
</script>
<template>
<div>
<div v-for="(value, index) of values" :key="index" mb-2 flex flex-nowrap gap-2>
<NInputNumber
:ref="refs.set"
v-model:value="values[index]"
:show-button="false"
placeholder="Set your measure..."
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>
</div>
<c-button @click="addValue">
<n-icon :component="Plus" depth="3" mr-2 size="18" />
Add a measure
</c-button>
</div>
</template>

View File

@@ -0,0 +1,12 @@
import { SpeedFilled } from '@vicons/material';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Benchmark builder',
path: '/benchmark-builder',
description: 'Easily compare execution time of tasks with this very simple online benchmark builder.',
keywords: ['benchmark', 'builder', 'execution', 'duration', 'mean', 'variance'],
component: () => import('./benchmark-builder.vue'),
icon: SpeedFilled,
createdAt: new Date('2023-04-05'),
});

View File

@@ -1,94 +1,37 @@
<template>
<div>
<n-card>
<n-grid cols="3" x-gap="12">
<n-gi span="1">
<n-form-item label="Language:">
<n-select
v-model:value="language"
:options="Object.keys(languages).map((label) => ({ label, value: label }))"
/>
</n-form-item>
</n-gi>
<n-gi span="2">
<n-form-item
label="Entropy (seed):"
:feedback="entropyValidation.message"
:validation-status="entropyValidation.status"
>
<n-input-group>
<n-input v-model:value="entropy" placeholder="Your string..." />
<n-button @click="refreshEntropy">
<n-icon size="22">
<Refresh />
</n-icon>
</n-button>
<n-button @click="copyEntropy">
<n-icon size="22">
<Copy />
</n-icon>
</n-button>
</n-input-group>
</n-form-item>
</n-gi>
</n-grid>
<n-form-item
label="Passphrase (mnemonic):"
:feedback="mnemonicValidation.message"
:validation-status="mnemonicValidation.status"
>
<n-input-group>
<n-input
v-model:value="passphrase"
style="text-align: center; flex: 1"
placeholder="Your mnemonic..."
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
/>
<n-button @click="copyPassphrase">
<n-icon size="22" :component="Copy" />
</n-button>
</n-input-group>
</n-form-item>
</n-card>
</div>
</template>
<script setup lang="ts">
import { useCopy } from '@/composable/copy';
import { ref, computed } from 'vue';
import {
entropyToMnemonic,
englishWordList,
chineseSimplifiedWordList,
chineseTraditionalWordList,
czechWordList,
englishWordList,
entropyToMnemonic,
frenchWordList,
generateEntropy,
italianWordList,
japaneseWordList,
koreanWordList,
mnemonicToEntropy,
portugueseWordList,
spanishWordList,
generateEntropy,
mnemonicToEntropy,
} from '@it-tools/bip39';
import { Copy, Refresh } from '@vicons/tabler';
import { useCopy } from '@/composable/copy';
import { useValidation } from '@/composable/validation';
import { isNotThrowing } from '@/utils/boolean';
import { withDefaultOnError } from '@/utils/defaults';
const languages = {
English: englishWordList,
'English': englishWordList,
'Chinese simplified': chineseSimplifiedWordList,
'Chinese traditional': chineseTraditionalWordList,
Czech: czechWordList,
French: frenchWordList,
Italian: italianWordList,
Japanese: japaneseWordList,
Korean: koreanWordList,
Portuguese: portugueseWordList,
Spanish: spanishWordList,
'Czech': czechWordList,
'French': frenchWordList,
'Italian': italianWordList,
'Japanese': japaneseWordList,
'Korean': koreanWordList,
'Portuguese': portugueseWordList,
'Spanish': spanishWordList,
};
const entropy = ref(generateEntropy());
@@ -97,20 +40,11 @@ const passphraseInput = ref('');
const language = ref<keyof typeof languages>('English');
const passphrase = computed({
get() {
try {
return entropyToMnemonic(entropy.value, languages[language.value]);
} catch (_) {
return passphraseInput.value;
}
return withDefaultOnError(() => entropyToMnemonic(entropy.value, languages[language.value]), passphraseInput.value);
},
set(value: string) {
passphraseInput.value = value;
try {
entropy.value = mnemonicToEntropy(value, languages[language.value]);
} catch (_) {
entropy.value = '';
}
entropy.value = withDefaultOnError(() => mnemonicToEntropy(value, languages[language.value]), '');
},
});
@@ -118,12 +52,12 @@ const entropyValidation = useValidation({
source: entropy,
rules: [
{
validator: (value) => value === '' || (value.length <= 32 && value.length >= 16 && value.length % 4 === 0),
validator: value => value === '' || (value.length <= 32 && value.length >= 16 && value.length % 4 === 0),
message: 'Entropy length should be >= 16, <= 32 and be a multiple of 4',
},
{
validator: (value) => /^[a-fA-F0-9]*$/.test(value),
message: 'Entropy should an hexadecimal number',
validator: value => /^[a-fA-F0-9]*$/.test(value),
message: 'Entropy should be an hexadecimal string',
},
],
});
@@ -132,14 +66,7 @@ const mnemonicValidation = useValidation({
source: passphrase,
rules: [
{
validator: (value) => {
try {
mnemonicToEntropy(value);
return true;
} catch (_) {
return false;
}
},
validator: value => isNotThrowing(() => mnemonicToEntropy(value, languages[language.value])),
message: 'Invalid mnemonic',
},
],
@@ -152,3 +79,53 @@ function refreshEntropy() {
const { copy: copyEntropy } = useCopy({ source: entropy, text: 'Entropy copied to the clipboard' });
const { copy: copyPassphrase } = useCopy({ source: passphrase, text: 'Passphrase copied to the clipboard' });
</script>
<template>
<div>
<n-grid cols="3" x-gap="12">
<n-gi span="1">
<c-select
v-model:value="language"
searchable
label="Language:"
:options="Object.keys(languages)"
/>
</n-gi>
<n-gi span="2">
<n-form-item
label="Entropy (seed):"
:feedback="entropyValidation.message"
:validation-status="entropyValidation.status"
>
<n-input-group>
<c-input-text v-model:value="entropy" placeholder="Your string..." />
<c-button @click="refreshEntropy()">
<n-icon size="22">
<Refresh />
</n-icon>
</c-button>
<c-button @click="copyEntropy()">
<n-icon size="22">
<Copy />
</n-icon>
</c-button>
</n-input-group>
</n-form-item>
</n-gi>
</n-grid>
<n-form-item
label="Passphrase (mnemonic):"
:feedback="mnemonicValidation.message"
:validation-status="mnemonicValidation.status"
>
<n-input-group>
<c-input-text v-model:value="passphrase" placeholder="Your mnemonic..." raw-text />
<c-button @click="copyPassphrase()">
<n-icon size="22" :component="Copy" />
</c-button>
</n-input-group>
</n-form-item>
</div>
</template>

View File

@@ -0,0 +1,214 @@
<script setup lang="ts">
import _ from 'lodash';
import { useMediaRecorder } from './useMediaRecorder';
interface Media { type: 'image' | 'video'; value: string; createdAt: Date }
const {
videoInputs: cameras,
audioInputs: microphones,
permissionGranted,
isSupported,
ensurePermissions,
} = useDevicesList({
requestPermissions: true,
constraints: { video: true, audio: true },
onUpdated() {
refreshCurrentDevices();
},
});
const video = ref<HTMLVideoElement>();
const medias = ref<Media[]>([]);
const currentCamera = ref(cameras.value[0]?.deviceId);
const currentMicrophone = ref(microphones.value[0]?.deviceId);
const permissionCannotBePrompted = ref(false);
const {
stream,
start,
enabled: isMediaStreamAvailable,
} = useUserMedia({
constraints: computed(() => ({
video: { deviceId: currentCamera.value },
...(currentMicrophone.value ? { audio: { deviceId: currentMicrophone.value } } : {}),
})),
autoSwitch: true,
});
const {
isRecordingSupported,
onRecordAvailable,
startRecording,
stopRecording,
pauseRecording,
recordingState,
resumeRecording,
} = useMediaRecorder({
stream,
});
onRecordAvailable((value) => {
medias.value.unshift({ type: 'video', value, createdAt: new Date() });
});
function refreshCurrentDevices() {
if (_.isNil(currentCamera) || !cameras.value.find(i => i.deviceId === currentCamera.value)) {
currentCamera.value = cameras.value[0]?.deviceId;
}
if (_.isNil(microphones) || !microphones.value.find(i => i.deviceId === currentMicrophone.value)) {
currentMicrophone.value = microphones.value[0]?.deviceId;
}
}
function takeScreenshot() {
if (!video.value) {
return;
}
const canvas = document.createElement('canvas');
canvas.width = video.value.videoWidth;
canvas.height = video.value.videoHeight;
canvas.getContext('2d')?.drawImage(video.value, 0, 0);
const image = canvas.toDataURL('image/png');
medias.value.unshift({ type: 'image', value: image, createdAt: new Date() });
}
watchEffect(() => {
if (video.value && stream.value) {
video.value.srcObject = stream.value;
}
});
async function requestPermissions() {
try {
await ensurePermissions();
}
catch (e) {
permissionCannotBePrompted.value = true;
}
}
function downloadMedia({ type, value, createdAt }: Media) {
const link = document.createElement('a');
link.href = value;
link.download = `${type}-${createdAt.getTime()}.${type === 'image' ? 'png' : 'webm'}`;
link.click();
}
</script>
<template>
<div>
<c-card v-if="!isSupported">
Your browser does not support recording video from camera
</c-card>
<c-card v-else-if="!permissionGranted" text-center>
You need to grant permission to use your camera and microphone
<c-alert v-if="permissionCannotBePrompted" mt-4 text-left>
Your browser has blocked permission request or does not support it. You need to grant permission manually in
your browser settings (usually the lock icon in the address bar).
</c-alert>
<div v-else mt-4 flex justify-center>
<c-button @click="requestPermissions">
Grant permission
</c-button>
</div>
</c-card>
<c-card v-else>
<div flex flex-col gap-2>
<c-select
v-model:value="currentCamera"
label-position="left"
label-width="60px"
label="Video:"
:options="cameras.map(({ deviceId, label }) => ({ value: deviceId, label }))"
placeholder="Select camera"
/>
<c-select
v-if="currentMicrophone && microphones.length > 0"
v-model:value="currentMicrophone"
label="Audio:"
label-position="left"
label-width="60px"
:options="microphones.map(({ deviceId, label }) => ({ value: deviceId, label }))"
placeholder="Select microphone"
/>
</div>
<div v-if="!isMediaStreamAvailable" mt-3 flex justify-center>
<c-button type="primary" @click="start">
Start webcam
</c-button>
</div>
<div v-else>
<div my-2>
<video ref="video" autoplay controls playsinline max-h-full w-full />
</div>
<div flex items-center justify-between gap-2>
<c-button :disabled="!isMediaStreamAvailable" @click="takeScreenshot">
<span mr-2> <icon-mdi-camera /></span>
Take screenshot
</c-button>
<div v-if="isRecordingSupported" flex justify-center gap-2>
<c-button v-if="recordingState === 'stopped'" @click="startRecording">
<span mr-2> <icon-mdi-video /></span>
Start recording
</c-button>
<c-button v-if="recordingState === 'recording'" @click="pauseRecording">
<span mr-2> <icon-mdi-pause /></span>
Pause
</c-button>
<c-button v-if="recordingState === 'paused'" @click="resumeRecording">
<span mr-2> <icon-mdi-play /></span>
Resume
</c-button>
<c-button v-if="recordingState !== 'stopped'" type="error" @click="stopRecording">
<span mr-2> <icon-mdi-record /></span>
Stop
</c-button>
</div>
<div v-else italic op-60>
Video recording is not supported in your browser
</div>
</div>
</div>
</c-card>
<div grid grid-cols-2 mt-5 gap-2>
<c-card v-for="({ type, value, createdAt }, index) in medias" :key="index">
<img v-if="type === 'image'" :src="value" max-h-full w-full alt="screenshot">
<video v-else :src="value" controls max-h-full w-full />
<div flex items-center justify-between>
<div font-bold>
{{ type === 'image' ? 'Screenshot' : 'Video' }}
</div>
<div flex gap-2>
<c-button @click="downloadMedia({ type, value, createdAt })">
<icon-mdi-download />
</c-button>
<c-button @click="medias = medias.filter((_ignored, i) => i !== index)">
<icon-mdi-delete-outline />
</c-button>
</div>
</div>
</c-card>
</div>
</div>
</template>

View File

@@ -0,0 +1,12 @@
import { Camera } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Camera recorder',
path: '/camera-recorder',
description: 'Take a picture or record a video from your webcam or camera.',
keywords: ['camera', 'recoder'],
component: () => import('./camera-recorder.vue'),
icon: Camera,
createdAt: new Date('2023-05-15'),
});

View File

@@ -0,0 +1,114 @@
import { type Ref, computed, ref } from 'vue';
export { useMediaRecorder };
function useMediaRecorder({ stream }: { stream: Ref<MediaStream | undefined> }): {
isRecordingSupported: Ref<boolean>
recordingState: Ref<'stopped' | 'recording' | 'paused'>
startRecording: () => void
stopRecording: () => void
pauseRecording: () => void
resumeRecording: () => void
onRecordAvailable: (cb: (url: string) => void) => void
} {
const isRecordingSupported = computed(() => MediaRecorder.isTypeSupported('video/webm'));
const mediaRecorder = ref<MediaRecorder | null>(null);
const recordedChunks = ref<Blob[]>([]);
const recordAvailable = createEventHook();
const recordingState = ref<'stopped' | 'recording' | 'paused'>('stopped');
const createVideo = () => {
const blob = new Blob(recordedChunks.value, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
recordedChunks.value = [];
return url;
};
const startRecording = () => {
if (!isRecordingSupported.value) {
return;
}
if (!stream.value) {
return;
}
if (recordingState.value !== 'stopped') {
return;
}
mediaRecorder.value = new MediaRecorder(stream.value, { mimeType: 'video/webm' });
mediaRecorder.value.ondataavailable = (e) => {
if (e.data.size > 0) {
recordedChunks.value.push(e.data);
}
};
mediaRecorder.value.onstop = () => {
recordAvailable.trigger(createVideo());
};
if (mediaRecorder.value.state !== 'inactive') {
return;
}
mediaRecorder.value.start();
recordingState.value = 'recording';
};
const stopRecording = () => {
if (!isRecordingSupported.value) {
return;
}
if (!mediaRecorder.value) {
return;
}
if (recordingState.value === 'stopped') {
return;
}
mediaRecorder.value.stop();
recordingState.value = 'stopped';
};
const pauseRecording = () => {
if (!isRecordingSupported.value) {
return;
}
if (!mediaRecorder.value) {
return;
}
if (recordingState.value !== 'recording') {
return;
}
mediaRecorder.value.pause();
recordingState.value = 'paused';
};
const resumeRecording = () => {
if (!isRecordingSupported.value) {
return;
}
if (!mediaRecorder.value) {
return;
}
if (recordingState.value !== 'paused') {
return;
}
mediaRecorder.value.resume();
recordingState.value = 'recording';
};
return {
isRecordingSupported,
startRecording,
stopRecording,
pauseRecording,
resumeRecording,
recordingState,
onRecordAvailable: recordAvailable.on,
};
}

View File

@@ -1,53 +1,4 @@
<template>
<n-card>
<n-form label-width="120" label-placement="left" :show-feedback="false">
<n-form-item label="Your string:">
<n-input v-model:value="input" />
</n-form-item>
<n-divider />
<n-form-item label="Camelcase:">
<input-copyable :value="camelCase(input)" />
</n-form-item>
<n-form-item label="Capitalcase:">
<input-copyable :value="capitalCase(input)" />
</n-form-item>
<n-form-item label="Constantcase:">
<input-copyable :value="constantCase(input)" />
</n-form-item>
<n-form-item label="Dotcase:">
<input-copyable :value="dotCase(input)" />
</n-form-item>
<n-form-item label="Headercase:">
<input-copyable :value="headerCase(input)" />
</n-form-item>
<n-form-item label="Nocase:">
<input-copyable :value="noCase(input)" />
</n-form-item>
<n-form-item label="Paramcase:">
<input-copyable :value="paramCase(input)" />
</n-form-item>
<n-form-item label="Pascalcase:">
<input-copyable :value="pascalCase(input)" />
</n-form-item>
<n-form-item label="Pathcase:">
<input-copyable :value="pathCase(input)" />
</n-form-item>
<n-form-item label="Sentencecase:">
<input-copyable :value="sentenceCase(input)" />
</n-form-item>
<n-form-item label="Snakecase:">
<input-copyable :value="snakeCase(input)" />
</n-form-item>
</n-form>
</n-card>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import InputCopyable from '../../components/InputCopyable.vue';
import {
camelCase,
capitalCase,
@@ -61,12 +12,95 @@ import {
sentenceCase,
snakeCase,
} from 'change-case';
import InputCopyable from '../../components/InputCopyable.vue';
const baseConfig = {
stripRegexp: /[^A-Za-zÀ-ÖØ-öø-ÿ]+/gi,
};
const input = ref('lorem ipsum dolor sit amet');
const formats = computed(() => [
{
label: 'Lowercase:',
value: noCase(input.value, baseConfig).toLocaleLowerCase(),
},
{
label: 'Uppercase:',
value: noCase(input.value, baseConfig).toLocaleUpperCase(),
},
{
label: 'Camelcase:',
value: camelCase(input.value, baseConfig),
},
{
label: 'Capitalcase:',
value: capitalCase(input.value, baseConfig),
},
{
label: 'Constantcase:',
value: constantCase(input.value, baseConfig),
},
{
label: 'Dotcase:',
value: dotCase(input.value, baseConfig),
},
{
label: 'Headercase:',
value: headerCase(input.value, baseConfig),
},
{
label: 'Nocase:',
value: noCase(input.value, baseConfig),
},
{
label: 'Paramcase:',
value: paramCase(input.value, baseConfig),
},
{
label: 'Pascalcase:',
value: pascalCase(input.value, baseConfig),
},
{
label: 'Pathcase:',
value: pathCase(input.value, baseConfig),
},
{
label: 'Sentencecase:',
value: sentenceCase(input.value, baseConfig),
},
{
label: 'Snakecase:',
value: snakeCase(input.value, baseConfig),
},
]);
const inputLabelAlignmentConfig = {
labelPosition: 'left',
labelWidth: '120px',
labelAlign: 'right',
};
</script>
<style lang="less" scoped>
.n-form-item {
margin: 5px 0;
}
</style>
<template>
<c-card>
<c-input-text
v-model:value="input"
label="Your string:"
placeholder="Your string..."
raw-text
v-bind="inputLabelAlignmentConfig"
/>
<div my-16px divider />
<InputCopyable
v-for="format in formats"
:key="format.label"
:value="format.value"
:label="format.label"
v-bind="inputLabelAlignmentConfig"
mb-1
/>
</c-card>
</template>

View File

@@ -0,0 +1,130 @@
import { describe, expect, it } from 'vitest';
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation } from './chmod-calculator.service';
describe('chmod-calculator', () => {
describe('computeChmodOctalRepresentation', () => {
it('get the octal representation from permissions', () => {
expect(
computeChmodOctalRepresentation({
permissions: {
owner: { read: true, write: true, execute: true },
group: { read: true, write: true, execute: true },
public: { read: true, write: true, execute: true },
},
}),
).to.eql('777');
expect(
computeChmodOctalRepresentation({
permissions: {
owner: { read: false, write: false, execute: false },
group: { read: false, write: false, execute: false },
public: { read: false, write: false, execute: false },
},
}),
).to.eql('000');
expect(
computeChmodOctalRepresentation({
permissions: {
owner: { read: false, write: true, execute: false },
group: { read: false, write: true, execute: true },
public: { read: true, write: false, execute: true },
},
}),
).to.eql('235');
expect(
computeChmodOctalRepresentation({
permissions: {
owner: { read: true, write: false, execute: false },
group: { read: false, write: true, execute: false },
public: { read: false, write: false, execute: true },
},
}),
).to.eql('421');
expect(
computeChmodOctalRepresentation({
permissions: {
owner: { read: false, write: false, execute: true },
group: { read: false, write: true, execute: false },
public: { read: true, write: false, execute: false },
},
}),
).to.eql('124');
expect(
computeChmodOctalRepresentation({
permissions: {
owner: { read: false, write: true, execute: false },
group: { read: false, write: true, execute: false },
public: { read: false, write: true, execute: false },
},
}),
).to.eql('222');
});
it('get the symbolic representation from permissions', () => {
expect(
computeChmodSymbolicRepresentation({
permissions: {
owner: { read: true, write: true, execute: true },
group: { read: true, write: true, execute: true },
public: { read: true, write: true, execute: true },
},
}),
).to.eql('rwxrwxrwx');
expect(
computeChmodSymbolicRepresentation({
permissions: {
owner: { read: false, write: false, execute: false },
group: { read: false, write: false, execute: false },
public: { read: false, write: false, execute: false },
},
}),
).to.eql('---------');
expect(
computeChmodSymbolicRepresentation({
permissions: {
owner: { read: false, write: true, execute: false },
group: { read: false, write: true, execute: true },
public: { read: true, write: false, execute: true },
},
}),
).to.eql('-w--wxr-x');
expect(
computeChmodSymbolicRepresentation({
permissions: {
owner: { read: true, write: false, execute: false },
group: { read: false, write: true, execute: false },
public: { read: false, write: false, execute: true },
},
}),
).to.eql('r---w---x');
expect(
computeChmodSymbolicRepresentation({
permissions: {
owner: { read: false, write: false, execute: true },
group: { read: false, write: true, execute: false },
public: { read: true, write: false, execute: false },
},
}),
).to.eql('--x-w-r--');
expect(
computeChmodSymbolicRepresentation({
permissions: {
owner: { read: false, write: true, execute: false },
group: { read: false, write: true, execute: false },
public: { read: false, write: true, execute: false },
},
}),
).to.eql('-w--w--w-');
});
});
});

View File

@@ -0,0 +1,30 @@
import _ from 'lodash';
import type { GroupPermissions, Permissions } from './chmod-calculator.types';
export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation };
function computeChmodOctalRepresentation({ permissions }: { permissions: Permissions }): string {
const permissionValue = { read: 4, write: 2, execute: 1 };
const getGroupPermissionValue = (permission: GroupPermissions) =>
_.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(permissionValue, key, 0) : 0), 0);
return [
getGroupPermissionValue(permissions.owner),
getGroupPermissionValue(permissions.group),
getGroupPermissionValue(permissions.public),
].join('');
}
function computeChmodSymbolicRepresentation({ permissions }: { permissions: Permissions }): string {
const permissionValue = { read: 'r', write: 'w', execute: 'x' };
const getGroupPermissionValue = (permission: GroupPermissions) =>
_.reduce(permission, (acc, isPermSet, key) => acc + (isPermSet ? _.get(permissionValue, key, '') : '-'), '');
return [
getGroupPermissionValue(permissions.owner),
getGroupPermissionValue(permissions.group),
getGroupPermissionValue(permissions.public),
].join('');
}

View File

@@ -0,0 +1,10 @@
export type Scope = 'read' | 'write' | 'execute';
export type Group = 'owner' | 'group' | 'public';
export type GroupPermissions = {
[k in Scope]: boolean;
};
export type Permissions = {
[k in Group]: GroupPermissions;
};

View File

@@ -0,0 +1,95 @@
<script setup lang="ts">
import { useThemeVars } from 'naive-ui';
import InputCopyable from '../../components/InputCopyable.vue';
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation } from './chmod-calculator.service';
import type { Group, Scope } from './chmod-calculator.types';
const themeVars = useThemeVars();
const scopes: { scope: Scope; title: string }[] = [
{ scope: 'read', title: 'Read (4)' },
{ scope: 'write', title: 'Write (2)' },
{ scope: 'execute', title: 'Execute (1)' },
];
const groups: Group[] = ['owner', 'group', 'public'];
const permissions = ref({
owner: { read: false, write: false, execute: false },
group: { read: false, write: false, execute: false },
public: { read: false, write: false, execute: false },
});
const octal = computed(() => computeChmodOctalRepresentation({ permissions: permissions.value }));
const symbolic = computed(() => computeChmodSymbolicRepresentation({ permissions: permissions.value }));
</script>
<template>
<div>
<n-table :bordered="false" :bottom-bordered="false" single-column class="permission-table">
<thead>
<tr>
<th class="text-center" scope="col" />
<th class="text-center" scope="col">
Owner (u)
</th>
<th class="text-center" scope="col">
Group (g)
</th>
<th class="text-center" scope="col">
Public (o)
</th>
</tr>
</thead>
<tbody>
<tr v-for="{ scope, title } of scopes" :key="scope">
<td class="line-header">
{{ title }}
</td>
<td v-for="group of groups" :key="group" class="text-center">
<!-- <n-switch v-model:value="permissions[group][scope]" /> -->
<n-checkbox v-model:checked="permissions[group][scope]" size="large" />
</td>
</tr>
</tbody>
</n-table>
<div class="octal-result">
{{ octal }}
</div>
<div class="octal-result">
{{ symbolic }}
</div>
<InputCopyable :value="`chmod ${octal} path`" readonly />
</div>
</template>
<style lang="less" scoped>
.octal-result {
text-align: center;
font-size: 50px;
font-family: monospace;
color: v-bind('themeVars.primaryColor');
margin: 20px 0;
}
.permission-table {
td,
th {
padding: 15px;
@media screen and (max-width: 600px) {
padding: 5px;
}
}
}
.line-header {
font-weight: bold;
text-align: right;
max-width: 80px;
}
.text-center {
text-align: center;
}
</style>

View File

@@ -0,0 +1,22 @@
import { FileInvoice } from '@vicons/tabler';
import { defineTool } from '../tool';
export const tool = defineTool({
name: 'Chmod calculator',
path: '/chmod-calculator',
description: 'Compute your chmod permissions and commands with this online chmod calculator.',
keywords: [
'chmod',
'calculator',
'file',
'permission',
'files',
'directory',
'folder',
'recursive',
'generator',
'octal',
],
component: () => import('./chmod-calculator.vue'),
icon: FileInvoice,
});

View File

@@ -1,12 +1,13 @@
import { describe, expect, it } from 'vitest';
import { formatChronometerTime } from './chronometer.service';
import { formatMs } from './chronometer.service';
describe('chronometer', () => {
describe('formatChronometerTime', () => {
it('format the elapsed time', () => {
expect(formatChronometerTime({ elapsed: 123456 })).toEqual('02:03.456');
expect(formatChronometerTime({ elapsed: 123456, msPerUnit: 100 })).toEqual('03:25:45.600');
expect(formatChronometerTime({ elapsed: 12345600 })).toEqual('03:25:45.600');
expect(formatMs(0)).toEqual('00:00.000');
expect(formatMs(1)).toEqual('00:00.001');
expect(formatMs(123456)).toEqual('02:03.456');
expect(formatMs(12345600)).toEqual('03:25:45.600');
});
});
});

View File

@@ -1,10 +1,8 @@
export function formatChronometerTime({ elapsed, msPerUnit = 1 }: { elapsed: number; msPerUnit?: number }) {
const elapsedMs = elapsed * msPerUnit;
const ms = elapsedMs % 1000;
const secs = ((elapsedMs - ms) / 1000) % 60;
const mins = (((elapsedMs - ms) / 1000 - secs) / 60) % 60;
const hrs = (((elapsedMs - ms) / 1000 - secs) / 60 - mins) / 60;
export function formatMs(msTotal: number) {
const ms = msTotal % 1000;
const secs = ((msTotal - ms) / 1000) % 60;
const mins = (((msTotal - ms) / 1000 - secs) / 60) % 60;
const hrs = (((msTotal - ms) / 1000 - secs) / 60 - mins) / 60;
const hrsString = hrs > 0 ? `${hrs.toString().padStart(2, '0')}:` : '';
return `${hrsString}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms

Some files were not shown because too many files have changed in this diff Show More