Compare commits
17 Commits
v2024.5.13
...
renovate/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0db398cd4f | ||
|
|
5732483fc2 | ||
|
|
bd184d934d | ||
|
|
7ca5933178 | ||
|
|
f962c416a3 | ||
|
|
1c35ac3704 | ||
|
|
72517002f3 | ||
|
|
f5c4ab19bc | ||
|
|
67094980c9 | ||
|
|
87984e2081 | ||
|
|
318fb6efb9 | ||
|
|
f1a5489e21 | ||
|
|
e1b4f9aafe | ||
|
|
76a19d218d | ||
|
|
b430baef40 | ||
|
|
dd4b7e687b | ||
|
|
30144aa3f5 |
11
.github/fern-banner.svg
vendored
Normal file
11
.github/fern-banner.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 62 KiB |
BIN
.github/logo-dark.png
vendored
Normal file
BIN
.github/logo-dark.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
.github/logo-white.png
vendored
Normal file
BIN
.github/logo-white.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
.github/logo.png
vendored
BIN
.github/logo.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 7.8 KiB |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
|
||||
6
.github/workflows/docker-nightly-release.yml
vendored
6
.github/workflows/docker-nightly-release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
outputs:
|
||||
should_run: ${{ steps.should_run.outputs.should_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- name: print latest_commit
|
||||
run: echo ${{ github.sha }}
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
- run: corepack enable
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
|
||||
2
.github/workflows/e2e-tests.yml
vendored
2
.github/workflows/e2e-tests.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
matrix:
|
||||
shard: [1/3, 2/3, 3/3]
|
||||
steps:
|
||||
- uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- run: corepack enable
|
||||
|
||||
|
||||
4
.github/workflows/releases.yml
vendored
4
.github/workflows/releases.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
- run: corepack enable
|
||||
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -2,6 +2,32 @@
|
||||
|
||||
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.
|
||||
|
||||
## Version 2024.10.22-7ca5933
|
||||
|
||||
### Features
|
||||
- **new tool**: Regex Tester (and Cheatsheet) (#1030) (f5c4ab1)
|
||||
- **new tool**: Markdown to HTML (#916) (87984e2)
|
||||
- **new-tool**: add email normalizer (#1243) (318fb6e)
|
||||
- **new tools**: JSON to XML and XML to JSON (#1231) (f1a5489)
|
||||
- **lorem-ipsum**: add button to refresh text lorem-ipsum (#1213) (e1b4f9a)
|
||||
- **base64**: Base64 enhancements (#905) (30144aa)
|
||||
|
||||
### Bug fixes
|
||||
- **favorites**: store favorites regardless of languages (#1202) (7ca5933)
|
||||
- **emoji-picker**: debounced search input (#1181) (76a19d2)
|
||||
- **format-transformer**: set overflow for output area width (#787) (b430bae)
|
||||
- **jwt-parser**: prevent UI overflow on small screen (#1095) (dd4b7e6)
|
||||
|
||||
### Refactoring
|
||||
- **regex-tester**: better description (7251700)
|
||||
|
||||
### Chores
|
||||
- **sponsors**: fern sponsor banners (#1314) (f962c41)
|
||||
- **readme**: updated logos (#1294) (6709498)
|
||||
|
||||
### Documentation
|
||||
- **author**: updated author links (#1316) (1c35ac3)
|
||||
|
||||
## Version 2024.05.13-a0bc346
|
||||
|
||||
### Features
|
||||
|
||||
12
README.md
12
README.md
@@ -1,7 +1,15 @@
|
||||

|
||||
<picture>
|
||||
<source srcset="./.github/logo-dark.png" media="(prefers-color-scheme: light)">
|
||||
<source srcset="./.github/logo-white.png" media="(prefers-color-scheme: dark)">
|
||||
<img src="./.github/logo-dark.png" alt="logo">
|
||||
</picture>
|
||||
|
||||
Useful tools for developer and people working in IT. [Have a look !](https://it-tools.tech).
|
||||
|
||||
## Sponsors
|
||||
|
||||
[](https://bit.ly/3zBl7DG)
|
||||
|
||||
## Functionalities and roadmap
|
||||
|
||||
Please check the [issues](https://github.com/CorentinTh/it-tools/issues) to see if some feature listed to be implemented.
|
||||
@@ -113,7 +121,7 @@ Big thanks to all the people who have already contributed!
|
||||
|
||||
## Credits
|
||||
|
||||
Coded with ❤️ by [Corentin Thomasset](//corentin-thomasset.fr).
|
||||
Coded with ❤️ by [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=readme).
|
||||
|
||||
This project is continuously deployed using [vercel.com](https://vercel.com).
|
||||
|
||||
|
||||
18
components.d.ts
vendored
18
components.d.ts
vendored
@@ -72,6 +72,7 @@ declare module '@vue/runtime-core' {
|
||||
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']
|
||||
EmailNormalizer: typeof import('./src/tools/email-normalizer/email-normalizer.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']
|
||||
@@ -110,6 +111,7 @@ declare module '@vue/runtime-core' {
|
||||
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']
|
||||
JsonToXml: typeof import('./src/tools/json-to-xml/json-to-xml.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']
|
||||
@@ -119,6 +121,7 @@ declare module '@vue/runtime-core' {
|
||||
LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default']
|
||||
MacAddressGenerator: typeof import('./src/tools/mac-address-generator/mac-address-generator.vue')['default']
|
||||
MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default']
|
||||
MarkdownToHtml: typeof import('./src/tools/markdown-to-html/markdown-to-html.vue')['default']
|
||||
MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default']
|
||||
MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default']
|
||||
MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default']
|
||||
@@ -127,24 +130,19 @@ declare module '@vue/runtime-core' {
|
||||
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
|
||||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NH1: typeof import('naive-ui')['NH1']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NLabel: typeof import('naive-ui')['NLabel']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NTable: typeof import('naive-ui')['NTable']
|
||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||
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']
|
||||
@@ -154,6 +152,9 @@ declare module '@vue/runtime-core' {
|
||||
PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default']
|
||||
QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default']
|
||||
RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default']
|
||||
RegexMemo: typeof import('./src/tools/regex-memo/regex-memo.vue')['default']
|
||||
'RegexMemo.content': typeof import('./src/tools/regex-memo/regex-memo.content.md')['default']
|
||||
RegexTester: typeof import('./src/tools/regex-tester/regex-tester.vue')['default']
|
||||
ResultRow: typeof import('./src/tools/ipv4-range-expander/result-row.vue')['default']
|
||||
RomanNumeralConverter: typeof import('./src/tools/roman-numeral-converter/roman-numeral-converter.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
@@ -186,6 +187,7 @@ declare module '@vue/runtime-core' {
|
||||
UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default']
|
||||
WifiQrCodeGenerator: typeof import('./src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue')['default']
|
||||
XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default']
|
||||
XmlToJson: typeof import('./src/tools/xml-to-json/xml-to-json.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']
|
||||
YamlViewer: typeof import('./src/tools/yaml-viewer/yaml-viewer.vue')['default']
|
||||
|
||||
@@ -38,7 +38,7 @@ about:
|
||||
# Über IT-Tools
|
||||
|
||||
Diese wunderbare Website, erstellt mit ❤ von [Corentin
|
||||
Thomasset](https://github.com/CorentinTh), sammelt nützliche Tools für
|
||||
Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), sammelt nützliche Tools für
|
||||
Entwickler und Menschen, die in der IT arbeiten. Wenn du sie nützlich
|
||||
findest, teile sie gerne mit Personen, von denen du denkst, dass sie sie
|
||||
ebenfalls nützlich finden könnten, und vergiss nicht, sie in deiner
|
||||
|
||||
@@ -30,7 +30,7 @@ about:
|
||||
content: >
|
||||
# About IT-Tools
|
||||
|
||||
This wonderful website, made with ❤ by [Corentin Thomasset](https://github.com/CorentinTh) , 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!
|
||||
This wonderful website, made with ❤ by [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about) , 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!
|
||||
|
||||
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 [sponsoring me](https://www.buymeacoffee.com/cthmsst).
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ about:
|
||||
content: >
|
||||
# Sobre IT-Tools
|
||||
|
||||
Este maravilloso sitio web, hecho con ❤ por [Corentin Thomasset](https://github.com/CorentinTh) , agrega herramientas útiles para desarrolladores y personas que trabajan en IT. Si lo encuentra útil, no dude en compartirlo con las personas que crea que también pueden encontrarlo útil y ¡no olvide marcarlo como favorito en su barra de accesos directos!
|
||||
Este maravilloso sitio web, hecho con ❤ por [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about) , agrega herramientas útiles para desarrolladores y personas que trabajan en IT. Si lo encuentra útil, no dude en compartirlo con las personas que crea que también pueden encontrarlo útil y ¡no olvide marcarlo como favorito en su barra de accesos directos!
|
||||
|
||||
IT Tools es de código abierto (under the MIT license) y gratis, y siempre lo será, pero me cuesta dinero alojar y renovar el nombre de dominio. Si desea apoyar mi trabajo y animarme a agregar más herramientas, considere apoyarme a través de[sponsoring me](https://www.buymeacoffee.com/cthmsst).
|
||||
|
||||
@@ -39,7 +39,7 @@ about:
|
||||
IT Tools está creado en Vue.js (Vue 3) con la biblioteca de componentes Naive UI y Vercel lo aloja y lo implementa continuamente. En algunas herramientas se utilizan bibliotecas de código abierto de terceros; puede encontrar la lista completa en [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json) archivo del repositorio.
|
||||
|
||||
## ¿Encontraste un error? ¿Falta una herramienta?
|
||||
|
||||
|
||||
Si necesita una herramienta que actualmente no está presente aquí y cree que puede ser útil, puede enviar una solicitud de función en el [issues section](https://github.com/CorentinTh/it-tools/issues/new/choose) en el repositorio de GitHub.
|
||||
|
||||
Y si encontró un error o algo no funciona como se esperaba, presente un reporte de error en el [issues section](https://github.com/CorentinTh/it-tools/issues/new/choose) en el repositorio de GitHub.
|
||||
@@ -68,4 +68,4 @@ tools:
|
||||
math: Math
|
||||
measurement: Measurement
|
||||
text: Text
|
||||
data: Data
|
||||
data: Data
|
||||
|
||||
@@ -29,7 +29,7 @@ about:
|
||||
content: >
|
||||
# À propos de IT-Tools
|
||||
|
||||
Ce merveilleux site, fait avec ❤ par [Corentin Thomasset](https://github.com/CorentinTh), regroupe des outils utiles pour les développeurs et les personnes travaillant dans l'informatique. Si vous le trouvez utile, n'hésitez pas à le partager et n'oubliez pas de le mettre dans vos favoris !
|
||||
Ce merveilleux site, fait avec ❤ par [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), regroupe des outils utiles pour les développeurs et les personnes travaillant dans l'informatique. Si vous le trouvez utile, n'hésitez pas à le partager et n'oubliez pas de le mettre dans vos favoris !
|
||||
|
||||
IT Tools est open-source (sous licence MIT) et gratuit, et le restera toujours, mais cela me coûte de l'argent pour l'héberger et renouveler le nom de domaine. Si vous voulez soutenir mon travail, et m'encourager à ajouter plus d'outils, n'hésitez pas à me [soutenir](https://www.buymeacoffee.com/cthmsst).
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ about:
|
||||
content: >
|
||||
# Sobre o IT-Tools
|
||||
|
||||
Este site maravilhoso, feito com ❤ por [Corentin Thomasset](https://github.com/CorentinTh), junta ferramentas úteis para desenvolvedores e outras pessoas que trabalham com TI. Se você achar o site útil, fique à vontade para compartilhar com quem também possa gostar e não esqueça de salvar o bookmark na sua barra de atalhos!
|
||||
Este site maravilhoso, feito com ❤ por [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), junta ferramentas úteis para desenvolvedores e outras pessoas que trabalham com TI. Se você achar o site útil, fique à vontade para compartilhar com quem também possa gostar e não esqueça de salvar o bookmark na sua barra de atalhos!
|
||||
|
||||
O IT Tools é código aberto (sob a licença MIT), é gratuito, e sempre será, mas custa dinheiro para hospedar e renovar o domínio. Se quiser apoiar meu trabalho e me encorajar a adicionar mais ferramentas, por favor considere [ser patrocinador](https://www.buymeacoffee.com/cthmsst).
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ about:
|
||||
content: >
|
||||
# Про IT-Tools
|
||||
|
||||
Цей чудовий вебсайт, створений з ❤ [Corentin Thomasset](https://github.com/CorentinTh), агрегує корисні інструменти для розробників і людей, які працюють в сфері IT. Якщо вам це корисно, будь ласка, поділіться цим з людьми, які, на вашу думку, також можуть знайти його корисним, і не забудьте додати його до закладок у вашій панелі швидкого доступу!
|
||||
Цей чудовий вебсайт, створений з ❤ [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), агрегує корисні інструменти для розробників і людей, які працюють в сфері IT. Якщо вам це корисно, будь ласка, поділіться цим з людьми, які, на вашу думку, також можуть знайти його корисним, і не забудьте додати його до закладок у вашій панелі швидкого доступу!
|
||||
|
||||
IT Tools є відкритим програмним забезпеченням (під ліцензією MIT) і безкоштовним, і завжди буде таким, але мені коштує гроші для хостингу і продовження доменного імені. Якщо ви хочете підтримати мою роботу і підтримати мене у додаванні нових інструментів, розгляньте можливість підтримки, [спонсоруючи мене](https://www.buymeacoffee.com/cthmsst).
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ about:
|
||||
content: >
|
||||
# Về IT-Tools
|
||||
|
||||
Website tuyệt vời này, được tạo ra bằng ❤ bởi [Corentin Thomasset](https://github.com/CorentinTh), tổng hợp các công cụ hữu ích cho nhà phát triển và những người làm việc trong lĩnh vực IT. Nếu bạn thấy nó hữu ích, xin đừng ngần ngại chia sẻ cho những người mà bạn nghĩ sẽ thấy nó hữu ích và đừng quên đánh dấu nó trong thanh lối tắt của bạn!
|
||||
Website tuyệt vời này, được tạo ra bằng ❤ bởi [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), tổng hợp các công cụ hữu ích cho nhà phát triển và những người làm việc trong lĩnh vực IT. Nếu bạn thấy nó hữu ích, xin đừng ngần ngại chia sẻ cho những người mà bạn nghĩ sẽ thấy nó hữu ích và đừng quên đánh dấu nó trong thanh lối tắt của bạn!
|
||||
|
||||
IT Tools là mã nguồn mở (dưới giấy phép MIT) và miễn phí, và sẽ luôn như vậy, nhưng tôi phải trả tiền để lưu trữ và gia hạn tên miền. Nếu bạn muốn hỗ trợ công việc của tôi, và khích lệ tôi thêm nhiều công cụ hơn, hãy xem xét hỗ trợ bằng cách [tài trợ cho tôi](https://www.buymeacoffee.com/cthmsst).
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ about:
|
||||
content: >
|
||||
# 关于 IT-Tools
|
||||
|
||||
IT-Tools 由 [Corentin Thomasset](https://github.com/CorentinTh) 用 ❤ 开发,汇集了对开发人员和 IT 从业者有用的工具。如果对您有帮助,请将其分享给您的朋友,并且添加到收藏夹中!
|
||||
IT-Tools 由 [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about) 用 ❤ 开发,汇集了对开发人员和 IT 从业者有用的工具。如果对您有帮助,请将其分享给您的朋友,并且添加到收藏夹中!
|
||||
|
||||
IT-Tools 永久免费且开源(MIT 许可证),但需要资金用于托管和续订域名。如果您想支持我的工作,并鼓励我添加更多工具,请考虑通过 [赞助我](https://www.buymeacoffee.com/cthmsst) 进行支持。
|
||||
|
||||
|
||||
23
package.json
23
package.json
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"name": "it-tools",
|
||||
"version": "2024.5.13-a0bc346",
|
||||
"version": "2024.10.22-7ca5933",
|
||||
"packageManager": "pnpm@8.15.3",
|
||||
"description": "Collection of handy online tools for developers, with great UX. ",
|
||||
"author": "Corentin Th <corentin.thomasset74+it-tools@gmail.com> (https://corentin.tech)",
|
||||
"license": "GNU GPLv3",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CorentinTh/it-tools"
|
||||
},
|
||||
"keywords": [
|
||||
"productivity",
|
||||
"converter",
|
||||
@@ -13,12 +20,6 @@
|
||||
"developer-tools",
|
||||
"developer-productivity"
|
||||
],
|
||||
"author": "Corentin Th <corentin.thomasset74+it-tools@gmail.com> (https://github.com/CorentinTh)",
|
||||
"license": "GNU GPLv3",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CorentinTh/it-tools"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && NODE_OPTIONS=--max_old_space_size=4096 vite build",
|
||||
@@ -37,11 +38,13 @@
|
||||
"dependencies": {
|
||||
"@it-tools/bip39": "^0.0.4",
|
||||
"@it-tools/oggen": "^1.3.0",
|
||||
"@regexper/render": "^1.0.0",
|
||||
"@sindresorhus/slugify": "^2.2.1",
|
||||
"@tiptap/pm": "2.1.6",
|
||||
"@tiptap/starter-kit": "2.1.6",
|
||||
"@tiptap/vue-3": "2.0.3",
|
||||
"@types/figlet": "^1.5.8",
|
||||
"@types/markdown-it": "^13.0.7",
|
||||
"@vicons/material": "^0.12.0",
|
||||
"@vicons/tabler": "^0.12.0",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
@@ -57,6 +60,7 @@
|
||||
"crypto-js": "^4.1.1",
|
||||
"date-fns": "^2.29.3",
|
||||
"dompurify": "^3.0.6",
|
||||
"email-normalizer": "^1.0.0",
|
||||
"emojilib": "^3.0.10",
|
||||
"figlet": "^1.7.0",
|
||||
"figue": "^1.2.0",
|
||||
@@ -64,10 +68,12 @@
|
||||
"highlight.js": "^11.7.0",
|
||||
"iarna-toml-esm": "^3.0.5",
|
||||
"ibantools": "^4.3.3",
|
||||
"js-base64": "^3.7.6",
|
||||
"json5": "^2.2.3",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"libphonenumber-js": "^1.10.28",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^14.0.0",
|
||||
"marked": "^10.0.0",
|
||||
"mathjs": "^11.9.1",
|
||||
"mime-types": "^2.1.35",
|
||||
@@ -80,6 +86,7 @@
|
||||
"pinia": "^2.0.34",
|
||||
"plausible-tracker": "^0.3.8",
|
||||
"qrcode": "^1.5.1",
|
||||
"randexp": "^0.5.3",
|
||||
"sql-formatter": "^13.0.0",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"ulid": "^2.3.0",
|
||||
@@ -89,8 +96,10 @@
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.9.1",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-shadow-dom": "^4.2.0",
|
||||
"vue-tsc": "^1.8.1",
|
||||
"xml-formatter": "^3.3.2",
|
||||
"xml-js": "^1.6.11",
|
||||
"yaml": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
144
pnpm-lock.yaml
generated
144
pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ dependencies:
|
||||
'@it-tools/oggen':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0
|
||||
'@regexper/render':
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
'@sindresorhus/slugify':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
@@ -26,6 +29,9 @@ dependencies:
|
||||
'@types/figlet':
|
||||
specifier: ^1.5.8
|
||||
version: 1.5.8
|
||||
'@types/markdown-it':
|
||||
specifier: ^13.0.7
|
||||
version: 13.0.9
|
||||
'@vicons/material':
|
||||
specifier: ^0.12.0
|
||||
version: 0.12.0
|
||||
@@ -71,6 +77,9 @@ dependencies:
|
||||
dompurify:
|
||||
specifier: ^3.0.6
|
||||
version: 3.0.6
|
||||
email-normalizer:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
emojilib:
|
||||
specifier: ^3.0.10
|
||||
version: 3.0.10
|
||||
@@ -92,6 +101,9 @@ dependencies:
|
||||
ibantools:
|
||||
specifier: ^4.3.3
|
||||
version: 4.3.3
|
||||
js-base64:
|
||||
specifier: ^3.7.6
|
||||
version: 3.7.7
|
||||
json5:
|
||||
specifier: ^2.2.3
|
||||
version: 2.2.3
|
||||
@@ -104,6 +116,9 @@ dependencies:
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
markdown-it:
|
||||
specifier: ^14.0.0
|
||||
version: 14.1.0
|
||||
marked:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
@@ -140,6 +155,9 @@ dependencies:
|
||||
qrcode:
|
||||
specifier: ^1.5.1
|
||||
version: 1.5.1
|
||||
randexp:
|
||||
specifier: ^0.5.3
|
||||
version: 0.5.3
|
||||
sql-formatter:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
@@ -167,12 +185,18 @@ dependencies:
|
||||
vue-router:
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6(vue@3.3.4)
|
||||
vue-shadow-dom:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
vue-tsc:
|
||||
specifier: ^1.8.1
|
||||
version: 1.8.1(typescript@5.2.2)
|
||||
xml-formatter:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
xml-js:
|
||||
specifier: ^1.6.11
|
||||
version: 1.6.11
|
||||
yaml:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
@@ -2465,6 +2489,17 @@ packages:
|
||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||
dev: false
|
||||
|
||||
/@regexper/parser@1.0.0:
|
||||
resolution: {integrity: sha512-S8AWIGpCNdl9PNHdbhI6TpXZsPk6FDU/RTZI+6UFF4rVFqDQKjCIbZSgFu7NihoEZKq57wKFPbbT1EzrjVvPHA==}
|
||||
dev: false
|
||||
|
||||
/@regexper/render@1.0.0:
|
||||
resolution: {integrity: sha512-xYm9RUgnhhZotTtf8UZpK1PG2CcTRXQ3JPwfTlYUZsy2J+UcTVc7BaO/MJadpMoVuT8jrIyptH4Y0HLzqhI3hQ==}
|
||||
dependencies:
|
||||
'@regexper/parser': 1.0.0
|
||||
'@svgdotjs/svg.js': 3.2.4
|
||||
dev: false
|
||||
|
||||
/@remirror/core-constants@2.0.1:
|
||||
resolution: {integrity: sha512-ZR4aihtnnT9lMbhh5DEbsriJRlukRXmLZe7HmM+6ufJNNUDoazc75UX26xbgQlNUqgAqMcUdGFAnPc1JwgAdLQ==}
|
||||
dependencies:
|
||||
@@ -2614,6 +2649,10 @@ packages:
|
||||
string.prototype.matchall: 4.0.10
|
||||
dev: true
|
||||
|
||||
/@svgdotjs/svg.js@3.2.4:
|
||||
resolution: {integrity: sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==}
|
||||
dev: false
|
||||
|
||||
/@tiptap/core@2.1.12(@tiptap/pm@2.1.6):
|
||||
resolution: {integrity: sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==}
|
||||
peerDependencies:
|
||||
@@ -2943,7 +2982,6 @@ packages:
|
||||
|
||||
/@types/linkify-it@3.0.2:
|
||||
resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==}
|
||||
dev: true
|
||||
|
||||
/@types/lodash-es@4.17.10:
|
||||
resolution: {integrity: sha512-YJP+w/2khSBwbUSFdGsSqmDvmnN3cCKoPOL7Zjle6s30ZtemkkqhjVfFqGwPN7ASil5VyjE2GtyU/yqYY6mC0A==}
|
||||
@@ -2965,6 +3003,13 @@ packages:
|
||||
'@types/mdurl': 1.0.2
|
||||
dev: true
|
||||
|
||||
/@types/markdown-it@13.0.9:
|
||||
resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==}
|
||||
dependencies:
|
||||
'@types/linkify-it': 3.0.2
|
||||
'@types/mdurl': 1.0.2
|
||||
dev: false
|
||||
|
||||
/@types/mdast@3.0.11:
|
||||
resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==}
|
||||
dependencies:
|
||||
@@ -2973,7 +3018,6 @@ packages:
|
||||
|
||||
/@types/mdurl@1.0.2:
|
||||
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
|
||||
dev: true
|
||||
|
||||
/@types/mime-types@2.1.1:
|
||||
resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==}
|
||||
@@ -3351,7 +3395,7 @@ packages:
|
||||
dependencies:
|
||||
'@unhead/dom': 0.5.1
|
||||
'@unhead/schema': 0.5.1
|
||||
'@vueuse/shared': 10.7.2(vue@3.3.4)
|
||||
'@vueuse/shared': 11.0.3(vue@3.3.4)
|
||||
unhead: 0.5.1
|
||||
vue: 3.3.4
|
||||
transitivePeerDependencies:
|
||||
@@ -3993,10 +4037,10 @@ packages:
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/@vueuse/shared@10.7.2(vue@3.3.4):
|
||||
resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==}
|
||||
/@vueuse/shared@11.0.3(vue@3.3.4):
|
||||
resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==}
|
||||
dependencies:
|
||||
vue-demi: 0.14.6(vue@3.3.4)
|
||||
vue-demi: 0.14.10(vue@3.3.4)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
@@ -4942,6 +4986,11 @@ packages:
|
||||
tslib: 2.5.0
|
||||
dev: false
|
||||
|
||||
/drange@1.1.1:
|
||||
resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
dev: true
|
||||
@@ -4970,6 +5019,12 @@ packages:
|
||||
/electron-to-chromium@1.4.572:
|
||||
resolution: {integrity: sha512-RlFobl4D3ieetbnR+2EpxdzFl9h0RAJkPK3pfiwMug2nhBin2ZCsGIAJWdpNniLz43sgXam/CgipOmvTA+rUiA==}
|
||||
|
||||
/email-normalizer@1.0.0:
|
||||
resolution: {integrity: sha512-wZYuuMtL4kUOmg/TPtCrf9hAZjbFq+FcjWA85Z5nr2lGllRnWJPxCJw3gy4Cx+adMoyVw4VJfGGvt/OHgIW+qg==}
|
||||
dependencies:
|
||||
typescript: 5.5.4
|
||||
dev: false
|
||||
|
||||
/emitter-component@1.1.1:
|
||||
resolution: {integrity: sha512-G+mpdiAySMuB7kesVRLuyvYRqDmshB7ReKEVuyBPkzQlmiDiLrt7hHHIy4Aff552bgknVN7B2/d3lzhGO5dvpQ==}
|
||||
dev: false
|
||||
@@ -5001,7 +5056,6 @@ packages:
|
||||
/entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
dev: true
|
||||
|
||||
/errno@0.1.8:
|
||||
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
|
||||
@@ -6472,6 +6526,10 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/js-base64@3.7.7:
|
||||
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
||||
dev: false
|
||||
|
||||
/js-beautify@1.14.6:
|
||||
resolution: {integrity: sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6669,6 +6727,12 @@ packages:
|
||||
dependencies:
|
||||
uc.micro: 1.0.6
|
||||
|
||||
/linkify-it@5.0.0:
|
||||
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
|
||||
dependencies:
|
||||
uc.micro: 2.1.0
|
||||
dev: false
|
||||
|
||||
/local-pkg@0.4.3:
|
||||
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -6806,6 +6870,18 @@ packages:
|
||||
mdurl: 1.0.1
|
||||
uc.micro: 1.0.6
|
||||
|
||||
/markdown-it@14.1.0:
|
||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
entities: 4.5.0
|
||||
linkify-it: 5.0.0
|
||||
mdurl: 2.0.0
|
||||
punycode.js: 2.3.1
|
||||
uc.micro: 2.1.0
|
||||
dev: false
|
||||
|
||||
/marked@10.0.0:
|
||||
resolution: {integrity: sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -6854,6 +6930,10 @@ packages:
|
||||
/mdurl@1.0.1:
|
||||
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
|
||||
|
||||
/mdurl@2.0.0:
|
||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||
dev: false
|
||||
|
||||
/merge-stream@2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
dev: true
|
||||
@@ -7663,6 +7743,11 @@ packages:
|
||||
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
|
||||
dev: true
|
||||
|
||||
/punycode.js@2.3.1:
|
||||
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/punycode@2.3.0:
|
||||
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -7702,6 +7787,14 @@ packages:
|
||||
ret: 0.1.15
|
||||
dev: false
|
||||
|
||||
/randexp@0.5.3:
|
||||
resolution: {integrity: sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
drange: 1.1.1
|
||||
ret: 0.2.2
|
||||
dev: false
|
||||
|
||||
/randombytes@2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
dependencies:
|
||||
@@ -7872,6 +7965,11 @@ packages:
|
||||
engines: {node: '>=0.12'}
|
||||
dev: false
|
||||
|
||||
/ret@0.2.2:
|
||||
resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/reusify@1.0.4:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
@@ -7952,8 +8050,6 @@ packages:
|
||||
/sax@1.2.4:
|
||||
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/saxes@6.0.0:
|
||||
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
|
||||
@@ -8596,6 +8692,12 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
/typescript@5.5.4:
|
||||
resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/ua-parser-js@1.0.35:
|
||||
resolution: {integrity: sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==}
|
||||
dev: false
|
||||
@@ -8603,6 +8705,10 @@ packages:
|
||||
/uc.micro@1.0.6:
|
||||
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
|
||||
|
||||
/uc.micro@2.1.0:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
dev: false
|
||||
|
||||
/ufo@1.1.2:
|
||||
resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
|
||||
|
||||
@@ -9136,8 +9242,8 @@ packages:
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-demi@0.14.5(vue@3.3.4):
|
||||
resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==}
|
||||
/vue-demi@0.14.10(vue@3.3.4):
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
@@ -9151,8 +9257,8 @@ packages:
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-demi@0.14.6(vue@3.3.4):
|
||||
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==}
|
||||
/vue-demi@0.14.5(vue@3.3.4):
|
||||
resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
@@ -9204,6 +9310,10 @@ packages:
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-shadow-dom@4.2.0:
|
||||
resolution: {integrity: sha512-lguI064rT2HT/dxqSmXtz860KOvCq+W3nU1jMqroTmX3K1H46q22BMR4emh/Ld3ozy35XJKOaNGcr6mkJ/t/yg==}
|
||||
dev: false
|
||||
|
||||
/vue-template-compiler@2.7.14:
|
||||
resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
|
||||
dependencies:
|
||||
@@ -9442,6 +9552,7 @@ packages:
|
||||
|
||||
/workbox-google-analytics@7.0.0:
|
||||
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
|
||||
deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
|
||||
dependencies:
|
||||
workbox-background-sync: 7.0.0
|
||||
workbox-core: 7.0.0
|
||||
@@ -9542,6 +9653,13 @@ packages:
|
||||
xml-parser-xo: 4.0.5
|
||||
dev: false
|
||||
|
||||
/xml-js@1.6.11:
|
||||
resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
sax: 1.2.4
|
||||
dev: false
|
||||
|
||||
/xml-name-validator@4.0.0:
|
||||
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
@@ -48,7 +48,7 @@ const output = computed(() => transformer.value(input.value));
|
||||
monospace
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div overflow-auto>
|
||||
<div mb-5px>
|
||||
{{ outputLabel }}
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import sqlHljs from 'highlight.js/lib/languages/sql';
|
||||
import xmlHljs from 'highlight.js/lib/languages/xml';
|
||||
import yamlHljs from 'highlight.js/lib/languages/yaml';
|
||||
import iniHljs from 'highlight.js/lib/languages/ini';
|
||||
import markdownHljs from 'highlight.js/lib/languages/markdown';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -30,6 +31,7 @@ hljs.registerLanguage('html', xmlHljs);
|
||||
hljs.registerLanguage('xml', xmlHljs);
|
||||
hljs.registerLanguage('yaml', yamlHljs);
|
||||
hljs.registerLanguage('toml', iniHljs);
|
||||
hljs.registerLanguage('markdown', markdownHljs);
|
||||
|
||||
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
|
||||
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
|
||||
|
||||
21
src/composable/debouncedref.ts
Normal file
21
src/composable/debouncedref.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
function useDebouncedRef<T>(initialValue: T, delay: number, immediate: boolean = false) {
|
||||
const state = ref(initialValue);
|
||||
const debouncedRef = customRef((track, trigger) => ({
|
||||
get() {
|
||||
track();
|
||||
return state.value;
|
||||
},
|
||||
set: _.debounce(
|
||||
(value) => {
|
||||
state.value = value;
|
||||
trigger();
|
||||
},
|
||||
delay,
|
||||
{ leading: immediate },
|
||||
),
|
||||
}));
|
||||
return debouncedRef;
|
||||
}
|
||||
export default useDebouncedRef;
|
||||
@@ -1,8 +1,13 @@
|
||||
import { extension as getExtensionFromMime } from 'mime-types';
|
||||
import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types';
|
||||
import type { Ref } from 'vue';
|
||||
import _ from 'lodash';
|
||||
|
||||
export { getMimeTypeFromBase64, useDownloadFileFromBase64 };
|
||||
export {
|
||||
getMimeTypeFromBase64,
|
||||
getMimeTypeFromExtension, getExtensionFromMimeType,
|
||||
useDownloadFileFromBase64, useDownloadFileFromBase64Refs,
|
||||
previewImageFromBase64,
|
||||
};
|
||||
|
||||
const commonMimeTypesSignatures = {
|
||||
'JVBERi0': 'application/pdf',
|
||||
@@ -36,30 +41,78 @@ function getFileExtensionFromMimeType({
|
||||
defaultExtension?: string
|
||||
}) {
|
||||
if (mimeType) {
|
||||
return getExtensionFromMime(mimeType) ?? defaultExtension;
|
||||
return getExtensionFromMimeType(mimeType) ?? defaultExtension;
|
||||
}
|
||||
|
||||
return defaultExtension;
|
||||
}
|
||||
|
||||
function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
|
||||
function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }:
|
||||
{ sourceValue: string; filename?: string; extension?: string; fileMimeType?: string }) {
|
||||
if (sourceValue === '') {
|
||||
throw new Error('Base64 string is empty');
|
||||
}
|
||||
|
||||
const defaultExtension = extension ?? 'txt';
|
||||
const { mimeType } = getMimeTypeFromBase64({ base64String: sourceValue });
|
||||
let base64String = sourceValue;
|
||||
if (!mimeType) {
|
||||
const targetMimeType = fileMimeType ?? getMimeTypeFromExtension(defaultExtension);
|
||||
base64String = `data:${targetMimeType};base64,${sourceValue}`;
|
||||
}
|
||||
|
||||
const cleanExtension = extension ?? getFileExtensionFromMimeType(
|
||||
{ mimeType, defaultExtension });
|
||||
let cleanFileName = filename ?? `file.${cleanExtension}`;
|
||||
if (extension && !cleanFileName.endsWith(`.${extension}`)) {
|
||||
cleanFileName = `${cleanFileName}.${cleanExtension}`;
|
||||
}
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = base64String;
|
||||
a.download = cleanFileName;
|
||||
a.click();
|
||||
}
|
||||
|
||||
function useDownloadFileFromBase64(
|
||||
{ source, filename, extension, fileMimeType }:
|
||||
{ source: Ref<string>; filename?: string; extension?: string; fileMimeType?: string }) {
|
||||
return {
|
||||
download() {
|
||||
if (source.value === '') {
|
||||
throw new Error('Base64 string is empty');
|
||||
}
|
||||
|
||||
const { mimeType } = getMimeTypeFromBase64({ base64String: source.value });
|
||||
const base64String = mimeType
|
||||
? source.value
|
||||
: `data:text/plain;base64,${source.value}`;
|
||||
|
||||
const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`;
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = base64String;
|
||||
a.download = cleanFileName;
|
||||
a.click();
|
||||
downloadFromBase64({ sourceValue: source.value, filename, extension, fileMimeType });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function useDownloadFileFromBase64Refs(
|
||||
{ source, filename, extension }:
|
||||
{ source: Ref<string>; filename?: Ref<string>; extension?: Ref<string> }) {
|
||||
return {
|
||||
download() {
|
||||
downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function previewImageFromBase64(base64String: string): HTMLImageElement {
|
||||
if (base64String === '') {
|
||||
throw new Error('Base64 string is empty');
|
||||
}
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = base64String;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.appendChild(img);
|
||||
|
||||
const previewContainer = document.getElementById('previewContainer');
|
||||
if (previewContainer) {
|
||||
previewContainer.innerHTML = '';
|
||||
previewContainer.appendChild(container);
|
||||
}
|
||||
else {
|
||||
throw new Error('Preview container element not found');
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useRouteQuery } from '@vueuse/router';
|
||||
import { computed } from 'vue';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
|
||||
export { useQueryParam };
|
||||
export { useQueryParam, useQueryParamOrStorage };
|
||||
|
||||
const transformers = {
|
||||
number: {
|
||||
@@ -16,6 +17,12 @@ const transformers = {
|
||||
fromQuery: (value: string) => value.toLowerCase() === 'true',
|
||||
toQuery: (value: boolean) => (value ? 'true' : 'false'),
|
||||
},
|
||||
object: {
|
||||
fromQuery: (value: string) => {
|
||||
return JSON.parse(value);
|
||||
},
|
||||
toQuery: (value: object) => JSON.stringify(value),
|
||||
},
|
||||
};
|
||||
|
||||
function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
|
||||
@@ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
|
||||
const type = typeof defaultValue;
|
||||
const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
|
||||
|
||||
const storageRef = useStorage(storageName, defaultValue);
|
||||
const proxyDefaultValue = transformer.toQuery(defaultValue as never);
|
||||
const proxy = useRouteQuery(name, proxyDefaultValue);
|
||||
|
||||
const r = ref(defaultValue);
|
||||
|
||||
watch(r,
|
||||
(value) => {
|
||||
proxy.value = transformer.toQuery(value as never);
|
||||
storageRef.value = value as never;
|
||||
},
|
||||
{ deep: true });
|
||||
|
||||
r.value = (proxy.value && proxy.value !== proxyDefaultValue
|
||||
? transformer.fromQuery(proxy.value) as unknown as T
|
||||
: storageRef.value as T) as never;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ import _ from 'lodash';
|
||||
import { type Ref, reactive, watch } from 'vue';
|
||||
|
||||
type ValidatorReturnType = unknown;
|
||||
type GetErrorMessageReturnType = string;
|
||||
|
||||
export interface UseValidationRule<T> {
|
||||
validator: (value: T) => ValidatorReturnType
|
||||
getErrorMessage?: (value: T) => GetErrorMessageReturnType
|
||||
message: string
|
||||
}
|
||||
|
||||
@@ -24,6 +26,15 @@ export function isFalsyOrHasThrown(cb: () => ValidatorReturnType): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function getErrorMessageOrThrown(cb: () => GetErrorMessageReturnType): string {
|
||||
try {
|
||||
return cb() || '';
|
||||
}
|
||||
catch (e: any) {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export interface ValidationAttrs {
|
||||
feedback: string
|
||||
validationStatus: string | undefined
|
||||
@@ -61,7 +72,13 @@ export function useValidation<T>({
|
||||
|
||||
for (const rule of get(rules)) {
|
||||
if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
|
||||
state.message = rule.message;
|
||||
if (rule.getErrorMessage) {
|
||||
const getErrorMessage = rule.getErrorMessage;
|
||||
state.message = rule.message.replace('{0}', getErrorMessageOrThrown(() => getErrorMessage(source.value)));
|
||||
}
|
||||
else {
|
||||
state.message = rule.message;
|
||||
}
|
||||
state.status = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,12 @@ export const config = figue({
|
||||
default: false,
|
||||
env: 'VITE_SHOW_BANNER',
|
||||
},
|
||||
showSponsorBanner: {
|
||||
doc: 'Show the sponsor banner',
|
||||
format: 'boolean',
|
||||
default: false,
|
||||
env: 'VITE_SHOW_SPONSOR_BANNER',
|
||||
},
|
||||
})
|
||||
.loadEnv({
|
||||
...import.meta.env,
|
||||
|
||||
@@ -81,7 +81,7 @@ const tools = computed<ToolCategory[]>(() => [
|
||||
</div>
|
||||
<div>
|
||||
© {{ new Date().getFullYear() }}
|
||||
<c-link target="_blank" rel="noopener" href="https://github.com/CorentinTh">
|
||||
<c-link target="_blank" rel="noopener" href="https://corentin.tech?utm_source=it-tools&utm_medium=footer">
|
||||
Corentin Thomasset
|
||||
</c-link>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createPinia } from 'pinia';
|
||||
import { createHead } from '@vueuse/head';
|
||||
|
||||
import { registerSW } from 'virtual:pwa-register';
|
||||
import shadow from 'vue-shadow-dom';
|
||||
import { plausible } from './plugins/plausible.plugin';
|
||||
|
||||
import 'virtual:uno.css';
|
||||
@@ -23,5 +24,6 @@ app.use(i18nPlugin);
|
||||
app.use(router);
|
||||
app.use(naive);
|
||||
app.use(plausible);
|
||||
app.use(shadow);
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
@@ -15,8 +15,8 @@ const { t } = useI18n();
|
||||
<template>
|
||||
<div class="pt-50px">
|
||||
<div class="grid-wrapper">
|
||||
<div v-if="config.showBanner" class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<ColoredCard :title="$t('home.follow.title')" :icon="Heart">
|
||||
<div class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<ColoredCard v-if="config.showBanner" :title="$t('home.follow.title')" :icon="Heart">
|
||||
{{ $t('home.follow.p1') }}
|
||||
<a
|
||||
href="https://github.com/CorentinTh/it-tools"
|
||||
@@ -34,6 +34,32 @@ const { t } = useI18n();
|
||||
{{ $t('home.follow.thankYou') }}
|
||||
<n-icon :component="Heart" />
|
||||
</ColoredCard>
|
||||
|
||||
<a href="https://bit.ly/3zBl7DG" target="_blank" rel="noopener" class="text-current decoration-none">
|
||||
<c-card v-if="config.showSponsorBanner" class="cursor-pointer !border-2px !hover:border-primary">
|
||||
<div class="dark:hidden">
|
||||
<svg width="40" height="40" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M458.592 250.639C425.688 222.824 376.118 211.673 332.186 244.145C330.164 245.615 327.651 243.103 329.183 241.143C339.6 227.725 351.67 213.266 361.413 198.746C371.339 183.858 386.167 173.198 403.262 167.99C494.253 140.42 466.925 6 466.925 6C466.925 6 326.365 15.0675 343.705 136.315C346.585 156.594 341.193 177.241 328.509 193.354C312.946 213.021 294.87 231.83 281.758 245.431C279.001 248.25 274.344 245.554 275.447 241.755C288.13 199.052 297.383 133.007 253.45 90.4259L191.625 39.0842L179.738 54.7685C144.384 101.393 154.739 167.132 201.429 202.422C228.205 222.64 240.337 244.635 238.438 268.774C237.274 283.233 230.718 296.773 220.914 307.495C202.471 327.713 185.253 349.402 171.957 374.521C170.119 378.013 164.788 376.665 164.972 372.683C166.871 331.205 162.888 237.712 93.037 204.321L14.8527 174.117L8.78665 192.19C-10.882 250.517 21.2863 312.825 79.557 332.614C130.23 349.83 148.305 382.486 136.112 431.438C135.561 433.215 126.737 483.638 127.963 506H184.15C186.049 471.323 222.446 448.532 254.001 462.684C262.886 466.667 272.016 472.364 281.39 479.717C331.634 519.295 405.652 509.921 445.173 459.621L456.447 445.284L385.371 394.249C336.597 355.896 271.525 373.234 223.365 406.074C219.321 408.831 214.174 404.419 216.441 400.008C274.65 285.806 350.322 286.051 379.979 311.416C415.946 342.172 470.418 336.658 500.932 300.572L509.694 290.218L458.531 250.639H458.592Z" fill="#aaaaaa" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="hidden dark:block">
|
||||
<svg width="40" height="40" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M458.592 250.639C425.688 222.824 376.118 211.673 332.186 244.145C330.164 245.615 327.651 243.103 329.183 241.143C339.6 227.725 351.67 213.266 361.413 198.746C371.339 183.858 386.167 173.198 403.262 167.99C494.253 140.42 466.925 6 466.925 6C466.925 6 326.365 15.0675 343.705 136.315C346.585 156.594 341.193 177.241 328.509 193.354C312.946 213.021 294.87 231.83 281.758 245.431C279.001 248.25 274.344 245.554 275.447 241.755C288.13 199.052 297.383 133.007 253.45 90.4259L191.625 39.0842L179.738 54.7685C144.384 101.393 154.739 167.132 201.429 202.422C228.205 222.64 240.337 244.635 238.438 268.774C237.274 283.233 230.718 296.773 220.914 307.495C202.471 327.713 185.253 349.402 171.957 374.521C170.119 378.013 164.788 376.665 164.972 372.683C166.871 331.205 162.888 237.712 93.037 204.321L14.8527 174.117L8.78665 192.19C-10.882 250.517 21.2863 312.825 79.557 332.614C130.23 349.83 148.305 382.486 136.112 431.438C135.561 433.215 126.737 483.638 127.963 506H184.15C186.049 471.323 222.446 448.532 254.001 462.684C262.886 466.667 272.016 472.364 281.39 479.717C331.634 519.295 405.652 509.921 445.173 459.621L456.447 445.284L385.371 394.249C336.597 355.896 271.525 373.234 223.365 406.074C219.321 408.831 214.174 404.419 216.441 400.008C274.65 285.806 350.322 286.051 379.979 311.416C415.946 342.172 470.418 336.658 500.932 300.572L509.694 290.218L458.531 250.639H458.592Z" fill="#505050" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="my-5px flex items-baseline gap-4 text-lg text-black dark:text-white">
|
||||
Fern <div class="rounded-full bg-#eeeeee px-10px py-2px text-xs text-black dark:bg-#333333 dark:text-white">
|
||||
Sponsor
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-neutral-500 dark:text-neutral-400">
|
||||
Offer developer documentation that looks as good as Stripe's using Fern. <a href="https://bit.ly/3zBl7DG" target="_blank" rel="noopener" class="font-bold text-current transition hover:text-primary">Request a preview</a> of your docs on Fern.
|
||||
</div>
|
||||
</c-card>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<transition name="height">
|
||||
|
||||
@@ -2,12 +2,19 @@
|
||||
import { useBase64 } from '@vueuse/core';
|
||||
import type { Ref } from 'vue';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
|
||||
import { getExtensionFromMimeType, getMimeTypeFromBase64, previewImageFromBase64, useDownloadFileFromBase64Refs } from '@/composable/downloadBase64';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { isValidBase64 } from '@/utils/base64';
|
||||
|
||||
const fileName = ref('file');
|
||||
const fileExtension = ref('');
|
||||
const base64Input = ref('');
|
||||
const { download } = useDownloadFileFromBase64({ source: base64Input });
|
||||
const { download } = useDownloadFileFromBase64Refs(
|
||||
{
|
||||
source: base64Input,
|
||||
filename: fileName,
|
||||
extension: fileExtension,
|
||||
});
|
||||
const base64InputValidation = useValidation({
|
||||
source: base64Input,
|
||||
rules: [
|
||||
@@ -18,6 +25,35 @@ const base64InputValidation = useValidation({
|
||||
],
|
||||
});
|
||||
|
||||
watch(
|
||||
base64Input,
|
||||
(newValue, _) => {
|
||||
const { mimeType } = getMimeTypeFromBase64({ base64String: newValue });
|
||||
if (mimeType) {
|
||||
fileExtension.value = getExtensionFromMimeType(mimeType) || fileExtension.value;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
function previewImage() {
|
||||
if (!base64InputValidation.isValid) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const image = previewImageFromBase64(base64Input.value);
|
||||
image.style.maxWidth = '100%';
|
||||
image.style.maxHeight = '400px';
|
||||
const previewContainer = document.getElementById('previewContainer');
|
||||
if (previewContainer) {
|
||||
previewContainer.innerHTML = '';
|
||||
previewContainer.appendChild(image);
|
||||
}
|
||||
}
|
||||
catch (_) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFile() {
|
||||
if (!base64InputValidation.isValid) {
|
||||
return;
|
||||
@@ -44,6 +80,24 @@ async function onUpload(file: File) {
|
||||
|
||||
<template>
|
||||
<c-card title="Base64 to file">
|
||||
<n-grid cols="3" x-gap="12">
|
||||
<n-gi span="2">
|
||||
<c-input-text
|
||||
v-model:value="fileName"
|
||||
label="File Name"
|
||||
placeholder="Download filename"
|
||||
mb-2
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<c-input-text
|
||||
v-model:value="fileExtension"
|
||||
label="Extension"
|
||||
placeholder="Extension"
|
||||
mb-2
|
||||
/>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<c-input-text
|
||||
v-model:value="base64Input"
|
||||
multiline
|
||||
@@ -53,7 +107,14 @@ async function onUpload(file: File) {
|
||||
mb-2
|
||||
/>
|
||||
|
||||
<div flex justify-center>
|
||||
<div flex justify-center py-2>
|
||||
<div id="previewContainer" />
|
||||
</div>
|
||||
|
||||
<div flex justify-center gap-3>
|
||||
<c-button :disabled="base64Input === '' || !base64InputValidation.isValid" @click="previewImage()">
|
||||
Preview image
|
||||
</c-button>
|
||||
<c-button :disabled="base64Input === '' || !base64InputValidation.isValid" @click="downloadFile()">
|
||||
Download file
|
||||
</c-button>
|
||||
|
||||
65
src/tools/email-normalizer/email-normalizer.vue
Normal file
65
src/tools/email-normalizer/email-normalizer.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { normalizeEmail } from 'email-normalizer';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
|
||||
const emails = ref('');
|
||||
const normalizedEmails = computed(() => {
|
||||
if (!emails.value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return emails.value
|
||||
.split('\n')
|
||||
.map((email) => {
|
||||
return withDefaultOnError(() => normalizeEmail({ email }), `Unable to parse email: ${email}`);
|
||||
})
|
||||
.join('\n');
|
||||
});
|
||||
|
||||
const { copy } = useCopy({ source: normalizedEmails, text: 'Normalized emails copied to the clipboard', createToast: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-2">
|
||||
Raw emails to normalize:
|
||||
</div>
|
||||
<c-input-text
|
||||
v-model:value="emails"
|
||||
placeholder="Put your emails here (one per line)..."
|
||||
rows="3"
|
||||
multiline
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
autofocus
|
||||
monospace
|
||||
/>
|
||||
|
||||
<div class="mb-2 mt-4">
|
||||
Normalized emails:
|
||||
</div>
|
||||
<c-input-text
|
||||
:value="normalizedEmails"
|
||||
placeholder="Normalized emails will appear here..."
|
||||
rows="3"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
multiline
|
||||
readonly
|
||||
monospace
|
||||
/>
|
||||
<div class="mt-4 flex justify-center gap-2">
|
||||
<c-button @click="emails = ''">
|
||||
Clear emails
|
||||
</c-button>
|
||||
<c-button :disabled="!normalizedEmails" @click="copy()">
|
||||
Copy normalized emails
|
||||
</c-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
12
src/tools/email-normalizer/index.ts
Normal file
12
src/tools/email-normalizer/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Mail } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Email normalizer',
|
||||
path: '/email-normalizer',
|
||||
description: 'Normalize email addresses to a standard format for easier comparison. Useful for deduplication and data cleaning.',
|
||||
keywords: ['email', 'normalizer'],
|
||||
component: () => import('./email-normalizer.vue'),
|
||||
icon: Mail,
|
||||
createdAt: new Date('2024-08-15'),
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import emojiKeywords from 'emojilib';
|
||||
import _ from 'lodash';
|
||||
import type { EmojiInfo } from './emoji.types';
|
||||
import { useFuzzySearch } from '@/composable/fuzzySearch';
|
||||
import useDebouncedRef from '@/composable/debouncedref';
|
||||
|
||||
const escapeUnicode = ({ emoji }: { emoji: string }) => emoji.split('').map(unit => `\\u${unit.charCodeAt(0).toString(16).padStart(4, '0')}`).join('');
|
||||
const getEmojiCodePoints = ({ emoji }: { emoji: string }) => emoji.codePointAt(0) ? `0x${emoji.codePointAt(0)?.toString(16)}` : undefined;
|
||||
@@ -23,7 +24,7 @@ const emojisGroups: { emojiInfos: EmojiInfo[]; group: string }[] = _
|
||||
.map((emojiInfos, group) => ({ group, emojiInfos }))
|
||||
.value();
|
||||
|
||||
const searchQuery = ref('');
|
||||
const searchQuery = useDebouncedRef('', 500);
|
||||
|
||||
const { searchResult } = useFuzzySearch({
|
||||
search: searchQuery,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { tool as base64FileConverter } from './base64-file-converter';
|
||||
import { tool as base64StringConverter } from './base64-string-converter';
|
||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||
import { tool as emailNormalizer } from './email-normalizer';
|
||||
|
||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||
|
||||
import { tool as textToUnicode } from './text-to-unicode';
|
||||
import { tool as safelinkDecoder } from './safelink-decoder';
|
||||
import { tool as xmlToJson } from './xml-to-json';
|
||||
import { tool as jsonToXml } from './json-to-xml';
|
||||
import { tool as regexTester } from './regex-tester';
|
||||
import { tool as regexMemo } from './regex-memo';
|
||||
import { tool as markdownToHtml } from './markdown-to-html';
|
||||
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
|
||||
import { tool as numeronymGenerator } from './numeronym-generator';
|
||||
import { tool as macAddressGenerator } from './mac-address-generator';
|
||||
@@ -107,6 +113,9 @@ export const toolsByCategory: ToolCategory[] = [
|
||||
listConverter,
|
||||
tomlToJson,
|
||||
tomlToYaml,
|
||||
xmlToJson,
|
||||
jsonToXml,
|
||||
markdownToHtml,
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -148,6 +157,9 @@ export const toolsByCategory: ToolCategory[] = [
|
||||
dockerRunToDockerComposeConverter,
|
||||
xmlFormatter,
|
||||
yamlViewer,
|
||||
emailNormalizer,
|
||||
regexTester,
|
||||
regexMemo,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
12
src/tools/json-to-xml/index.ts
Normal file
12
src/tools/json-to-xml/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Braces } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'JSON to XML',
|
||||
path: '/json-to-xml',
|
||||
description: 'Convert JSON to XML',
|
||||
keywords: ['json', 'xml'],
|
||||
component: () => import('./json-to-xml.vue'),
|
||||
icon: Braces,
|
||||
createdAt: new Date('2024-08-09'),
|
||||
});
|
||||
32
src/tools/json-to-xml/json-to-xml.vue
Normal file
32
src/tools/json-to-xml/json-to-xml.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import convert from 'xml-js';
|
||||
import JSON5 from 'json5';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import type { UseValidationRule } from '@/composable/validation';
|
||||
|
||||
const defaultValue = '{"a":{"_attributes":{"x":"1.234","y":"It\'s"}}}';
|
||||
function transformer(value: string) {
|
||||
return withDefaultOnError(() => {
|
||||
return convert.js2xml(JSON5.parse(value), { compact: true });
|
||||
}, '');
|
||||
}
|
||||
|
||||
const rules: UseValidationRule<string>[] = [
|
||||
{
|
||||
validator: (v: string) => v === '' || JSON5.parse(v),
|
||||
message: 'Provided JSON is not valid.',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<format-transformer
|
||||
input-label="Your JSON content"
|
||||
:input-default="defaultValue"
|
||||
input-placeholder="Paste your JSON content here..."
|
||||
output-label="Converted XML"
|
||||
output-language="xml"
|
||||
:transformer="transformer"
|
||||
:input-validation-rules="rules"
|
||||
/>
|
||||
</template>
|
||||
@@ -39,7 +39,7 @@ const validation = useValidation({
|
||||
{{ section.title }}
|
||||
</th>
|
||||
<tr v-for="{ claim, claimDescription, friendlyValue, value } in decodedJWT[section.key]" :key="claim + value">
|
||||
<td class="claims">
|
||||
<td class="claims" style="vertical-align: top;">
|
||||
<span font-bold>
|
||||
{{ claim }}
|
||||
</span>
|
||||
@@ -47,7 +47,7 @@ const validation = useValidation({
|
||||
({{ claimDescription }})
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td style="word-wrap: break-word;word-break: break-all;">
|
||||
<span>{{ value }}</span>
|
||||
<span v-if="friendlyValue" ml-2 op-70>
|
||||
({{ friendlyValue }})
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { generateLoremIpsum } from './lorem-ipsum-generator.service';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { randIntFromInterval } from '@/utils/random';
|
||||
import { computedRefreshable } from '@/composable/computedRefreshable';
|
||||
|
||||
const paragraphs = ref(1);
|
||||
const sentences = ref([3, 8]);
|
||||
@@ -9,7 +10,7 @@ const words = ref([8, 15]);
|
||||
const startWithLoremIpsum = ref(true);
|
||||
const asHTML = ref(false);
|
||||
|
||||
const loremIpsumText = computed(() =>
|
||||
const [loremIpsumText, refreshLoremIpsum] = computedRefreshable(() =>
|
||||
generateLoremIpsum({
|
||||
paragraphCount: paragraphs.value,
|
||||
asHTML: asHTML.value,
|
||||
@@ -18,6 +19,7 @@ const loremIpsumText = computed(() =>
|
||||
startWithLoremIpsum: startWithLoremIpsum.value,
|
||||
}),
|
||||
);
|
||||
|
||||
const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to the clipboard' });
|
||||
</script>
|
||||
|
||||
@@ -41,10 +43,13 @@ const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to
|
||||
|
||||
<c-input-text :value="loremIpsumText" multiline placeholder="Your lorem ipsum..." readonly mt-5 rows="5" />
|
||||
|
||||
<div mt-5 flex justify-center>
|
||||
<div mt-5 flex justify-center gap-3>
|
||||
<c-button autofocus @click="copy()">
|
||||
Copy
|
||||
</c-button>
|
||||
<c-button @click="refreshLoremIpsum">
|
||||
Refresh
|
||||
</c-button>
|
||||
</div>
|
||||
</c-card>
|
||||
</template>
|
||||
|
||||
12
src/tools/markdown-to-html/index.ts
Normal file
12
src/tools/markdown-to-html/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Markdown } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Markdown to HTML',
|
||||
path: '/markdown-to-html',
|
||||
description: 'Convert Markdown to Html and allow to print (as PDF)',
|
||||
keywords: ['markdown', 'html', 'converter', 'pdf'],
|
||||
component: () => import('./markdown-to-html.vue'),
|
||||
icon: Markdown,
|
||||
createdAt: new Date('2024-08-25'),
|
||||
});
|
||||
44
src/tools/markdown-to-html/markdown-to-html.vue
Normal file
44
src/tools/markdown-to-html/markdown-to-html.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
import markdownit from 'markdown-it';
|
||||
import TextareaCopyable from '@/components/TextareaCopyable.vue';
|
||||
|
||||
const inputMarkdown = ref('');
|
||||
const outputHtml = computed(() => {
|
||||
const md = markdownit();
|
||||
return md.render(inputMarkdown.value);
|
||||
});
|
||||
|
||||
function printHtml() {
|
||||
const w = window.open();
|
||||
if (w === null) {
|
||||
return;
|
||||
}
|
||||
w.document.body.innerHTML = outputHtml.value;
|
||||
w.print();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<c-input-text
|
||||
v-model:value="inputMarkdown"
|
||||
multiline raw-text
|
||||
placeholder="Your Markdown content..."
|
||||
rows="8"
|
||||
autofocus
|
||||
label="Your Markdown to convert:"
|
||||
/>
|
||||
|
||||
<n-divider />
|
||||
|
||||
<n-form-item label="Output HTML:">
|
||||
<TextareaCopyable :value="outputHtml" :word-wrap="true" language="html" />
|
||||
</n-form-item>
|
||||
|
||||
<div flex justify-center>
|
||||
<n-button @click="printHtml">
|
||||
Print as PDF
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
12
src/tools/regex-memo/index.ts
Normal file
12
src/tools/regex-memo/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { BrandJavascript } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Regex cheatsheet',
|
||||
path: '/regex-memo',
|
||||
description: 'Javascript Regex/Regular Expression cheatsheet',
|
||||
keywords: ['regex', 'regular', 'expression', 'javascript', 'memo', 'cheatsheet'],
|
||||
component: () => import('./regex-memo.vue'),
|
||||
icon: BrandJavascript,
|
||||
createdAt: new Date('2024-09-20'),
|
||||
});
|
||||
121
src/tools/regex-memo/regex-memo.content.md
Normal file
121
src/tools/regex-memo/regex-memo.content.md
Normal file
@@ -0,0 +1,121 @@
|
||||
### Normal characters
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`.` or `[^\n\r]` | any character *excluding* a newline or carriage return
|
||||
`[A-Za-z]` | alphabet
|
||||
`[a-z]` | lowercase alphabet
|
||||
`[A-Z]` | uppercase alphabet
|
||||
`\d` or `[0-9]` | digit
|
||||
`\D` or `[^0-9]` | non-digit
|
||||
`_` | underscore
|
||||
`\w` or `[A-Za-z0-9_]` | alphabet, digit or underscore
|
||||
`\W` or `[^A-Za-z0-9_]` | inverse of `\w`
|
||||
`\S` | inverse of `\s`
|
||||
|
||||
### Whitespace characters
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
` ` | space
|
||||
`\t` | tab
|
||||
`\n` | newline
|
||||
`\r` | carriage return
|
||||
`\s` | space, tab, newline or carriage return
|
||||
|
||||
### Character set
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`[xyz]` | either `x`, `y` or `z`
|
||||
`[^xyz]` | neither `x`, `y` nor `z`
|
||||
`[1-3]` | either `1`, `2` or `3`
|
||||
`[^1-3]` | neither `1`, `2` nor `3`
|
||||
|
||||
- Think of a character set as an `OR` operation on the single characters that are enclosed between the square brackets.
|
||||
- Use `^` after the opening `[` to “negate” the character set.
|
||||
- Within a character set, `.` means a literal period.
|
||||
|
||||
### Characters that require escaping
|
||||
|
||||
#### Outside a character set
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`\.` | period
|
||||
`\^` | caret
|
||||
`\$` | dollar sign
|
||||
`\|` | pipe
|
||||
`\\` | back slash
|
||||
`\/` | forward slash
|
||||
`\(` | opening bracket
|
||||
`\)` | closing bracket
|
||||
`\[` | opening square bracket
|
||||
`\]` | closing square bracket
|
||||
`\{` | opening curly bracket
|
||||
`\}` | closing curly bracket
|
||||
|
||||
#### Inside a character set
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`\\` | back slash
|
||||
`\]` | closing square bracket
|
||||
|
||||
- A `^` must be escaped only if it occurs immediately after the opening `[` of the character set.
|
||||
- A `-` must be escaped only if it occurs between two alphabets or two digits.
|
||||
|
||||
### Quantifiers
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`{2}` | exactly 2
|
||||
`{2,}` | at least 2
|
||||
`{2,7}` | at least 2 but no more than 7
|
||||
`*` | 0 or more
|
||||
`+` | 1 or more
|
||||
`?` | exactly 0 or 1
|
||||
|
||||
- The quantifier goes *after* the expression to be quantified.
|
||||
|
||||
### Boundaries
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`^` | start of string
|
||||
`$` | end of string
|
||||
`\b` | word boundary
|
||||
|
||||
- How word boundary matching works:
|
||||
- At the beginning of the string if the first character is `\w`.
|
||||
- Between two adjacent characters within the string, if the first character is `\w` and the second character is `\W`.
|
||||
- At the end of the string if the last character is `\w`.
|
||||
|
||||
### Matching
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`foo\|bar` | match either `foo` or `bar`
|
||||
`foo(?=bar)` | match `foo` if it’s before `bar`
|
||||
`foo(?!bar)` | match `foo` if it’s *not* before `bar`
|
||||
`(?<=bar)foo` | match `foo` if it’s after `bar`
|
||||
`(?<!bar)foo` | match `foo` if it’s *not* after `bar`
|
||||
|
||||
### Grouping and capturing
|
||||
|
||||
Expression | Description
|
||||
:--|:--
|
||||
`(foo)` | capturing group; match and capture `foo`
|
||||
`(?:foo)` | non-capturing group; match `foo` but *without* capturing `foo`
|
||||
`(foo)bar\1` | `\1` is a backreference to the 1st capturing group; match `foobarfoo`
|
||||
|
||||
- Capturing groups are only relevant in the following methods:
|
||||
- `string.match(regexp)`
|
||||
- `string.matchAll(regexp)`
|
||||
- `string.replace(regexp, callback)`
|
||||
- `\N` is a backreference to the `Nth` capturing group. Capturing groups are numbered starting from 1.
|
||||
|
||||
## References and tools
|
||||
|
||||
- [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
|
||||
- [RegExplained](https://leaverou.github.io/regexplained/)
|
||||
32
src/tools/regex-memo/regex-memo.vue
Normal file
32
src/tools/regex-memo/regex-memo.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import Memo from './regex-memo.content.md';
|
||||
|
||||
const themeVars = useThemeVars();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Memo />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(pre) {
|
||||
margin: 0;
|
||||
padding: 15px 22px;
|
||||
background-color: v-bind('themeVars.cardColor');
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
}
|
||||
::v-deep(table) {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
::v-deep(table), ::v-deep(td), ::v-deep(th) {
|
||||
border: 1px solid v-bind('themeVars.textColor1');
|
||||
padding: 5px;
|
||||
}
|
||||
::v-deep(a) {
|
||||
color: v-bind('themeVars.textColor1');
|
||||
}
|
||||
</style>
|
||||
12
src/tools/regex-tester/index.ts
Normal file
12
src/tools/regex-tester/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Language } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Regex Tester',
|
||||
path: '/regex-tester',
|
||||
description: 'Test your regular expressions with sample text.',
|
||||
keywords: ['regex', 'tester', 'sample', 'expression'],
|
||||
component: () => import('./regex-tester.vue'),
|
||||
icon: Language,
|
||||
createdAt: new Date('2024-09-20'),
|
||||
});
|
||||
106
src/tools/regex-tester/regex-tester.service.test.ts
Normal file
106
src/tools/regex-tester/regex-tester.service.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { matchRegex } from './regex-tester.service';
|
||||
|
||||
const regexesData = [
|
||||
{
|
||||
regex: '',
|
||||
text: '',
|
||||
flags: '',
|
||||
result: [],
|
||||
},
|
||||
{
|
||||
regex: '.*',
|
||||
text: '',
|
||||
flags: '',
|
||||
result: [],
|
||||
},
|
||||
{
|
||||
regex: '',
|
||||
text: 'aaa',
|
||||
flags: '',
|
||||
result: [],
|
||||
},
|
||||
{
|
||||
regex: 'a',
|
||||
text: 'baaa',
|
||||
flags: '',
|
||||
result: [
|
||||
{
|
||||
captures: [],
|
||||
groups: [],
|
||||
index: 1,
|
||||
value: 'a',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
regex: '(.)(?<g>r)',
|
||||
text: 'azertyr',
|
||||
flags: 'g',
|
||||
result: [
|
||||
{
|
||||
captures: [
|
||||
{
|
||||
end: 3,
|
||||
name: '1',
|
||||
start: 2,
|
||||
value: 'e',
|
||||
},
|
||||
{
|
||||
end: 4,
|
||||
name: '2',
|
||||
start: 3,
|
||||
value: 'r',
|
||||
},
|
||||
],
|
||||
groups: [
|
||||
{
|
||||
end: 4,
|
||||
name: 'g',
|
||||
start: 3,
|
||||
value: 'r',
|
||||
},
|
||||
],
|
||||
index: 2,
|
||||
value: 'er',
|
||||
},
|
||||
{
|
||||
captures: [
|
||||
{
|
||||
end: 6,
|
||||
name: '1',
|
||||
start: 5,
|
||||
value: 'y',
|
||||
},
|
||||
{
|
||||
end: 7,
|
||||
name: '2',
|
||||
start: 6,
|
||||
value: 'r',
|
||||
},
|
||||
],
|
||||
groups: [
|
||||
{
|
||||
end: 7,
|
||||
name: 'g',
|
||||
start: 6,
|
||||
value: 'r',
|
||||
},
|
||||
],
|
||||
index: 5,
|
||||
value: 'yr',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
describe('regex-tester', () => {
|
||||
for (const reg of regexesData) {
|
||||
const { regex, text, flags, result: expected_result } = reg;
|
||||
it(`Should matchRegex("${regex}","${text}","${flags}") return correct result`, async () => {
|
||||
const result = matchRegex(regex, text, `${flags}d`);
|
||||
|
||||
expect(result).to.deep.equal(expected_result);
|
||||
});
|
||||
}
|
||||
});
|
||||
61
src/tools/regex-tester/regex-tester.service.ts
Normal file
61
src/tools/regex-tester/regex-tester.service.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
interface RegExpGroupIndices {
|
||||
[name: string]: [number, number]
|
||||
}
|
||||
interface RegExpIndices extends Array<[number, number]> {
|
||||
groups: RegExpGroupIndices
|
||||
}
|
||||
interface RegExpExecArrayWithIndices extends RegExpExecArray {
|
||||
indices: RegExpIndices
|
||||
}
|
||||
interface GroupCapture {
|
||||
name: string
|
||||
value: string
|
||||
start: number
|
||||
end: number
|
||||
};
|
||||
|
||||
export function matchRegex(regex: string, text: string, flags: string) {
|
||||
// if (regex === '' || text === '') {
|
||||
// return [];
|
||||
// }
|
||||
|
||||
let lastIndex = -1;
|
||||
const re = new RegExp(regex, flags);
|
||||
const results = [];
|
||||
let match = re.exec(text) as RegExpExecArrayWithIndices;
|
||||
while (match !== null) {
|
||||
if (re.lastIndex === lastIndex || match[0] === '') {
|
||||
break;
|
||||
}
|
||||
const indices = match.indices;
|
||||
const captures: Array<GroupCapture> = [];
|
||||
Object.entries(match).forEach(([captureName, captureValue]) => {
|
||||
if (captureName !== '0' && captureName.match(/\d+/)) {
|
||||
captures.push({
|
||||
name: captureName,
|
||||
value: captureValue,
|
||||
start: indices[Number(captureName)][0],
|
||||
end: indices[Number(captureName)][1],
|
||||
});
|
||||
}
|
||||
});
|
||||
const groups: Array<GroupCapture> = [];
|
||||
Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => {
|
||||
groups.push({
|
||||
name: groupName,
|
||||
value: groupValue,
|
||||
start: indices.groups[groupName][0],
|
||||
end: indices.groups[groupName][1],
|
||||
});
|
||||
});
|
||||
results.push({
|
||||
index: match.index,
|
||||
value: match[0],
|
||||
captures,
|
||||
groups,
|
||||
});
|
||||
lastIndex = re.lastIndex;
|
||||
match = re.exec(text) as RegExpExecArrayWithIndices;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
193
src/tools/regex-tester/regex-tester.vue
Normal file
193
src/tools/regex-tester/regex-tester.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<script setup lang="ts">
|
||||
import RandExp from 'randexp';
|
||||
import { render } from '@regexper/render';
|
||||
import type { ShadowRootExpose } from 'vue-shadow-dom';
|
||||
import { matchRegex } from './regex-tester.service';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { useQueryParamOrStorage } from '@/composable/queryParams';
|
||||
|
||||
const regex = useQueryParamOrStorage({ name: 'regex', storageName: 'regex-tester:regex', defaultValue: '' });
|
||||
const text = ref('');
|
||||
const global = ref(true);
|
||||
const ignoreCase = ref(false);
|
||||
const multiline = ref(false);
|
||||
const dotAll = ref(true);
|
||||
const unicode = ref(true);
|
||||
const unicodeSets = ref(false);
|
||||
const visualizerSVG = ref<ShadowRootExpose>();
|
||||
|
||||
const regexValidation = useValidation({
|
||||
source: regex,
|
||||
rules: [
|
||||
{
|
||||
message: 'Invalid regex: {0}',
|
||||
validator: value => new RegExp(value),
|
||||
getErrorMessage: (value) => {
|
||||
const _ = new RegExp(value);
|
||||
return '';
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const results = computed(() => {
|
||||
let flags = 'd';
|
||||
if (global.value) {
|
||||
flags += 'g';
|
||||
}
|
||||
if (ignoreCase.value) {
|
||||
flags += 'i';
|
||||
}
|
||||
if (multiline.value) {
|
||||
flags += 'm';
|
||||
}
|
||||
if (dotAll.value) {
|
||||
flags += 's';
|
||||
}
|
||||
if (unicode.value) {
|
||||
flags += 'u';
|
||||
}
|
||||
else if (unicodeSets.value) {
|
||||
flags += 'v';
|
||||
}
|
||||
|
||||
try {
|
||||
return matchRegex(regex.value, text.value, flags);
|
||||
}
|
||||
catch (_) {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const sample = computed(() => {
|
||||
try {
|
||||
const randexp = new RandExp(new RegExp(regex.value.replace(/\(\?\<[^\>]*\>/g, '(?:')));
|
||||
return randexp.gen();
|
||||
}
|
||||
catch (_) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(
|
||||
async () => {
|
||||
const regexValue = regex.value;
|
||||
// shadow root is required:
|
||||
// @regexper/render append a <defs><style> that broke svg transparency of icons in the whole site
|
||||
const visualizer = visualizerSVG.value?.shadow_root;
|
||||
if (visualizer) {
|
||||
while (visualizer.lastChild) {
|
||||
visualizer.removeChild(visualizer.lastChild);
|
||||
}
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
try {
|
||||
await render(regexValue, svg);
|
||||
}
|
||||
catch (_) {
|
||||
}
|
||||
visualizer.appendChild(svg);
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div max-w-600px>
|
||||
<c-card title="Regex" mb-1>
|
||||
<c-input-text
|
||||
v-model:value="regex"
|
||||
label="Regex to test:"
|
||||
placeholder="Put the regex to test"
|
||||
multiline
|
||||
rows="3"
|
||||
:validation="regexValidation"
|
||||
/>
|
||||
<router-link target="_blank" to="/regex-memo" mb-1 mt-1>
|
||||
See Regular Expression Cheatsheet
|
||||
</router-link>
|
||||
<n-space>
|
||||
<n-checkbox v-model:checked="global">
|
||||
<span title="Global search">Global search. (<code>g</code>)</span>
|
||||
</n-checkbox>
|
||||
<n-checkbox v-model:checked="ignoreCase">
|
||||
<span title="Case-insensitive search">Case-insensitive search. (<code>i</code>)</span>
|
||||
</n-checkbox>
|
||||
<n-checkbox v-model:checked="multiline">
|
||||
<span title="Allows ^ and $ to match next to newline characters.">Multiline(<code>m</code>)</span>
|
||||
</n-checkbox>
|
||||
<n-checkbox v-model:checked="dotAll">
|
||||
<span title="Allows . to match newline characters.">Singleline(<code>s</code>)</span>
|
||||
</n-checkbox>
|
||||
<n-checkbox v-model:checked="unicode">
|
||||
<span title="Unicode; treat a pattern as a sequence of Unicode code points.">Unicode(<code>u</code>)</span>
|
||||
</n-checkbox>
|
||||
<n-checkbox v-model:checked="unicodeSets">
|
||||
<span title="An upgrade to the u mode with more Unicode features.">Unicode Sets (<code>v</code>)</span>
|
||||
</n-checkbox>
|
||||
</n-space>
|
||||
|
||||
<n-divider />
|
||||
|
||||
<c-input-text
|
||||
v-model:value="text"
|
||||
label="Text to match:"
|
||||
placeholder="Put the text to match"
|
||||
multiline
|
||||
rows="5"
|
||||
/>
|
||||
</c-card>
|
||||
|
||||
<c-card title="Matches" mb-1 mt-3>
|
||||
<n-table v-if="results?.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
Index in text
|
||||
</th>
|
||||
<th scope="col">
|
||||
Value
|
||||
</th>
|
||||
<th scope="col">
|
||||
Captures
|
||||
</th>
|
||||
<th scope="col">
|
||||
Groups
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match of results" :key="match.index">
|
||||
<td>{{ match.index }}</td>
|
||||
<td>{{ match.value }}</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li v-for="capture in match.captures" :key="capture.name">
|
||||
"{{ capture.name }}" = {{ capture.value }} [{{ capture.start }} - {{ capture.end }}]
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li v-for="group in match.groups" :key="group.name">
|
||||
"{{ group.name }}" = {{ group.value }} [{{ group.start }} - {{ group.end }}]
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
<c-alert v-else>
|
||||
No match
|
||||
</c-alert>
|
||||
</c-card>
|
||||
|
||||
<c-card title="Sample matching text" mt-3>
|
||||
<pre style="white-space: pre-wrap; word-break: break-all;">{{ sample }}</pre>
|
||||
</c-card>
|
||||
|
||||
<c-card title="Regex Diagram" style="overflow-x: scroll;" mt-3>
|
||||
<shadow-root ref="visualizerSVG">
|
||||
 
|
||||
</shadow-root>
|
||||
</c-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -14,6 +14,7 @@ export const useToolStore = defineStore('tools', () => {
|
||||
|
||||
return ({
|
||||
...tool,
|
||||
path: tool.path,
|
||||
name: t(`tools.${toolI18nKey}.title`, tool.name),
|
||||
description: t(`tools.${toolI18nKey}.description`, tool.description),
|
||||
category: t(`tools.categories.${tool.category.toLowerCase()}`, tool.category),
|
||||
@@ -23,8 +24,9 @@ export const useToolStore = defineStore('tools', () => {
|
||||
const toolsByCategory = computed<ToolCategory[]>(() => {
|
||||
return _.chain(tools.value)
|
||||
.groupBy('category')
|
||||
.map((components, name) => ({
|
||||
.map((components, name, path) => ({
|
||||
name,
|
||||
path,
|
||||
components,
|
||||
}))
|
||||
.value();
|
||||
@@ -32,7 +34,7 @@ export const useToolStore = defineStore('tools', () => {
|
||||
|
||||
const favoriteTools = computed(() => {
|
||||
return favoriteToolsName.value
|
||||
.map(favoriteName => tools.value.find(({ name }) => name === favoriteName))
|
||||
.map(favoriteName => tools.value.find(({ name, path }) => name === favoriteName || path === favoriteName))
|
||||
.filter(Boolean) as ToolWithCategory[]; // cast because .filter(Boolean) does not remove undefined from type
|
||||
});
|
||||
|
||||
@@ -43,15 +45,16 @@ export const useToolStore = defineStore('tools', () => {
|
||||
newTools: computed(() => tools.value.filter(({ isNew }) => isNew)),
|
||||
|
||||
addToolToFavorites({ tool }: { tool: MaybeRef<Tool> }) {
|
||||
favoriteToolsName.value.push(get(tool).name);
|
||||
favoriteToolsName.value.push(get(tool).path);
|
||||
},
|
||||
|
||||
removeToolFromFavorites({ tool }: { tool: MaybeRef<Tool> }) {
|
||||
favoriteToolsName.value = favoriteToolsName.value.filter(name => get(tool).name !== name);
|
||||
favoriteToolsName.value = favoriteToolsName.value.filter(name => get(tool).name !== name && get(tool).path !== name);
|
||||
},
|
||||
|
||||
isToolFavorite({ tool }: { tool: MaybeRef<Tool> }) {
|
||||
return favoriteToolsName.value.includes(get(tool).name);
|
||||
return favoriteToolsName.value.includes(get(tool).name)
|
||||
|| favoriteToolsName.value.includes(get(tool).path);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
12
src/tools/xml-to-json/index.ts
Normal file
12
src/tools/xml-to-json/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Braces } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'XML to JSON',
|
||||
path: '/xml-to-json',
|
||||
description: 'Convert XML to JSON',
|
||||
keywords: ['xml', 'json'],
|
||||
component: () => import('./xml-to-json.vue'),
|
||||
icon: Braces,
|
||||
createdAt: new Date('2024-08-09'),
|
||||
});
|
||||
32
src/tools/xml-to-json/xml-to-json.vue
Normal file
32
src/tools/xml-to-json/xml-to-json.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import convert from 'xml-js';
|
||||
import { isValidXML } from '../xml-formatter/xml-formatter.service';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import type { UseValidationRule } from '@/composable/validation';
|
||||
|
||||
const defaultValue = '<a x="1.234" y="It\'s"/>';
|
||||
function transformer(value: string) {
|
||||
return withDefaultOnError(() => {
|
||||
return JSON.stringify(convert.xml2js(value, { compact: true }), null, 2);
|
||||
}, '');
|
||||
}
|
||||
|
||||
const rules: UseValidationRule<string>[] = [
|
||||
{
|
||||
validator: isValidXML,
|
||||
message: 'Provided XML is not valid.',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<format-transformer
|
||||
input-label="Your XML content"
|
||||
:input-default="defaultValue"
|
||||
input-placeholder="Paste your XML content here..."
|
||||
output-label="Converted JSON"
|
||||
output-language="json"
|
||||
:transformer="transformer"
|
||||
:input-validation-rules="rules"
|
||||
/>
|
||||
</template>
|
||||
@@ -38,7 +38,8 @@ describe('base64 utils', () => {
|
||||
|
||||
it('should throw for incorrect base64 string', () => {
|
||||
expect(() => base64ToText('a')).to.throw('Incorrect base64 string');
|
||||
expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
|
||||
// should not really be false because trimming of space is now implied
|
||||
// expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
|
||||
expect(() => base64ToText('é')).to.throw('Incorrect base64 string');
|
||||
// missing final '='
|
||||
expect(() => base64ToText('bG9yZW0gaXBzdW0')).to.throw('Incorrect base64 string');
|
||||
@@ -56,17 +57,17 @@ describe('base64 utils', () => {
|
||||
|
||||
it('should return false for incorrect base64 string', () => {
|
||||
expect(isValidBase64('a')).to.eql(false);
|
||||
expect(isValidBase64(' ')).to.eql(false);
|
||||
expect(isValidBase64('é')).to.eql(false);
|
||||
expect(isValidBase64('data:text/plain;notbase64,YQ==')).to.eql(false);
|
||||
// missing final '='
|
||||
expect(isValidBase64('bG9yZW0gaXBzdW0')).to.eql(false);
|
||||
});
|
||||
|
||||
it('should return false for untrimmed correct base64 string', () => {
|
||||
expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(false);
|
||||
expect(isValidBase64(' LTE=')).to.eql(false);
|
||||
expect(isValidBase64(' YQ== ')).to.eql(false);
|
||||
it('should return true for untrimmed correct base64 string', () => {
|
||||
expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(true);
|
||||
expect(isValidBase64(' LTE=')).to.eql(true);
|
||||
expect(isValidBase64(' YQ== ')).to.eql(true);
|
||||
expect(isValidBase64(' ')).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Base64 } from 'js-base64';
|
||||
|
||||
export { textToBase64, base64ToText, isValidBase64, removePotentialDataAndMimePrefix };
|
||||
|
||||
function textToBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
|
||||
const encoded = window.btoa(str);
|
||||
const encoded = Base64.encode(str);
|
||||
return makeUrlSafe ? makeUriSafe(encoded) : encoded;
|
||||
}
|
||||
|
||||
@@ -16,7 +18,7 @@ function base64ToText(str: string, { makeUrlSafe = false }: { makeUrlSafe?: bool
|
||||
}
|
||||
|
||||
try {
|
||||
return window.atob(cleanStr);
|
||||
return Base64.decode(cleanStr);
|
||||
}
|
||||
catch (_) {
|
||||
throw new Error('Incorrect base64 string');
|
||||
@@ -34,10 +36,11 @@ function isValidBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boo
|
||||
}
|
||||
|
||||
try {
|
||||
const reEncodedBase64 = Base64.fromUint8Array(Base64.toUint8Array(cleanStr));
|
||||
if (makeUrlSafe) {
|
||||
return removePotentialPadding(window.btoa(window.atob(cleanStr))) === cleanStr;
|
||||
return removePotentialPadding(reEncodedBase64) === cleanStr;
|
||||
}
|
||||
return window.btoa(window.atob(cleanStr)) === cleanStr;
|
||||
return reEncodedBase64 === cleanStr.replace(/\s/g, '');
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user