mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-20 14:56:17 -04:00
Compare commits
28 commits
v2024.5.13
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
07eea0f484 | ||
![]() |
a4ab7db9e4 | ||
![]() |
08d977b8cd | ||
![]() |
63fbd3b45c | ||
![]() |
b47d132839 | ||
![]() |
0b1b98f93e | ||
![]() |
ea8c4ed077 | ||
![]() |
ae1363937b | ||
![]() |
c7b80fbc78 | ||
![]() |
131497322d | ||
![]() |
f836666417 | ||
![]() |
aa8cba96de | ||
![]() |
5732483fc2 | ||
![]() |
bd184d934d | ||
![]() |
7ca5933178 | ||
![]() |
f962c416a3 | ||
![]() |
1c35ac3704 | ||
![]() |
72517002f3 | ||
![]() |
f5c4ab19bc | ||
![]() |
67094980c9 | ||
![]() |
87984e2081 | ||
![]() |
318fb6efb9 | ||
![]() |
f1a5489e21 | ||
![]() |
e1b4f9aafe | ||
![]() |
76a19d218d | ||
![]() |
b430baef40 | ||
![]() |
dd4b7e687b | ||
![]() |
30144aa3f5 |
58 changed files with 10353 additions and 5968 deletions
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 |
10
.github/sponsor-banner.svg
vendored
Normal file
10
.github/sponsor-banner.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 57 KiB |
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.
|
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
|
## Version 2024.05.13-a0bc346
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
10
README.md
10
README.md
|
@ -1,4 +1,8 @@
|
||||||

|
<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).
|
Useful tools for developer and people working in IT. [Have a look !](https://it-tools.tech).
|
||||||
|
|
||||||
|
@ -109,11 +113,11 @@ It will create a directory in `src/tools` with the correct files, and a the impo
|
||||||
|
|
||||||
Big thanks to all the people who have already contributed!
|
Big thanks to all the people who have already contributed!
|
||||||
|
|
||||||
[](https://github.com/corentinth/it-tools/graphs/contributors)
|
[](https://github.com/corentinth/it-tools/graphs/contributors)
|
||||||
|
|
||||||
## Credits
|
## 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).
|
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']
|
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']
|
DynamicValues: typeof import('./src/tools/benchmark-builder/dynamic-values.vue')['default']
|
||||||
Editor: typeof import('./src/tools/html-wysiwyg-editor/editor/editor.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']
|
EmojiCard: typeof import('./src/tools/emoji-picker/emoji-card.vue')['default']
|
||||||
EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default']
|
EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default']
|
||||||
EmojiPicker: typeof import('./src/tools/emoji-picker/emoji-picker.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']
|
JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default']
|
||||||
JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.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']
|
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']
|
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']
|
JsonViewer: typeof import('./src/tools/json-viewer/json-viewer.vue')['default']
|
||||||
JwtParser: typeof import('./src/tools/jwt-parser/jwt-parser.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']
|
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']
|
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']
|
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']
|
MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default']
|
||||||
MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.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']
|
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']
|
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
|
||||||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||||
NavbarButtons: typeof import('./src/components/NavbarButtons.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']
|
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
NDivider: typeof import('naive-ui')['NDivider']
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
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']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
|
||||||
NLabel: typeof import('naive-ui')['NLabel']
|
|
||||||
NLayout: typeof import('naive-ui')['NLayout']
|
NLayout: typeof import('naive-ui')['NLayout']
|
||||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||||
NMenu: typeof import('naive-ui')['NMenu']
|
NMenu: typeof import('naive-ui')['NMenu']
|
||||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
NSpin: typeof import('naive-ui')['NSpin']
|
NTable: typeof import('naive-ui')['NTable']
|
||||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
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']
|
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']
|
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']
|
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']
|
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']
|
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']
|
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']
|
RomanNumeralConverter: typeof import('./src/tools/roman-numeral-converter/roman-numeral-converter.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
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']
|
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']
|
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']
|
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']
|
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']
|
YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default']
|
||||||
YamlViewer: typeof import('./src/tools/yaml-viewer/yaml-viewer.vue')['default']
|
YamlViewer: typeof import('./src/tools/yaml-viewer/yaml-viewer.vue')['default']
|
||||||
|
|
|
@ -10,6 +10,7 @@ home:
|
||||||
newestTools: Neueste Tools
|
newestTools: Neueste Tools
|
||||||
favoriteTools: Deine Lieblingstools
|
favoriteTools: Deine Lieblingstools
|
||||||
allTools: Alle Tools
|
allTools: Alle Tools
|
||||||
|
favoritesDndToolTip: 'Ziehen und Ablegen, um Favoriten neu zu ordnen'
|
||||||
subtitle: Praktische Tools für Entwickler
|
subtitle: Praktische Tools für Entwickler
|
||||||
toggleMenu: Menü umschalten
|
toggleMenu: Menü umschalten
|
||||||
home: Startseite
|
home: Startseite
|
||||||
|
@ -21,13 +22,13 @@ home:
|
||||||
p1: Gib uns einen Stern auf
|
p1: Gib uns einen Stern auf
|
||||||
githubRepository: IT-Tools GitHub-Repository
|
githubRepository: IT-Tools GitHub-Repository
|
||||||
p2: oder folge uns auf
|
p2: oder folge uns auf
|
||||||
twitterAccount: IT-Tools Twitter-Konto
|
twitterXAccount: IT-Tools X-Konto
|
||||||
thankYou: Vielen Dank!
|
thankYou: Vielen Dank!
|
||||||
nav:
|
nav:
|
||||||
github: GitHub-Repository
|
github: GitHub-Repository
|
||||||
githubRepository: IT-Tools GitHub-Repository
|
githubRepository: IT-Tools GitHub-Repository
|
||||||
twitter: Twitter-Konto
|
twitterX: X-Konto
|
||||||
twitterAccount: IT-Tools Twitter-Konto
|
twitterXAccount: IT-Tools X-Konto
|
||||||
about: Über IT-Tools
|
about: Über IT-Tools
|
||||||
aboutLabel: Über
|
aboutLabel: Über
|
||||||
darkMode: Dunkelmodus
|
darkMode: Dunkelmodus
|
||||||
|
@ -38,13 +39,13 @@ about:
|
||||||
# Über IT-Tools
|
# Über IT-Tools
|
||||||
|
|
||||||
Diese wunderbare Website, erstellt mit ❤ von [Corentin
|
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
|
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
|
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
|
ebenfalls nützlich finden könnten, und vergiss nicht, sie in deiner
|
||||||
Lesezeichenleiste zu speichern!
|
Lesezeichenleiste zu speichern!
|
||||||
|
|
||||||
IT-Tools ist Open Source (unter der MIT-Lizenz) und kostenlos und wird es
|
IT-Tools ist Open Source (unter der GPL-3.0-Lizenz) und kostenlos und wird es
|
||||||
immer sein, aber es kostet mich Geld, die Website zu hosten und den
|
immer sein, aber es kostet mich Geld, die Website zu hosten und den
|
||||||
Domainnamen zu erneuern. Wenn du meine Arbeit unterstützen möchtest und mich
|
Domainnamen zu erneuern. Wenn du meine Arbeit unterstützen möchtest und mich
|
||||||
ermutigen möchtest, mehr Tools hinzuzufügen, überlege bitte, mich durch
|
ermutigen möchtest, mehr Tools hinzuzufügen, überlege bitte, mich durch
|
||||||
|
|
|
@ -3,6 +3,7 @@ home:
|
||||||
newestTools: Newest tools
|
newestTools: Newest tools
|
||||||
favoriteTools: 'Your favorite tools'
|
favoriteTools: 'Your favorite tools'
|
||||||
allTools: 'All the tools'
|
allTools: 'All the tools'
|
||||||
|
favoritesDndToolTip: 'Drag and drop to reorder favorites'
|
||||||
subtitle: 'Handy tools for developers'
|
subtitle: 'Handy tools for developers'
|
||||||
toggleMenu: 'Toggle menu'
|
toggleMenu: 'Toggle menu'
|
||||||
home: Home
|
home: Home
|
||||||
|
@ -14,13 +15,13 @@ home:
|
||||||
p1: 'Give us a star on'
|
p1: 'Give us a star on'
|
||||||
githubRepository: 'IT-Tools GitHub repository'
|
githubRepository: 'IT-Tools GitHub repository'
|
||||||
p2: 'or follow us on'
|
p2: 'or follow us on'
|
||||||
twitterAccount: 'IT-Tools Twitter account'
|
twitterXAccount: 'IT-Tools X account'
|
||||||
thankYou: 'Thank you!'
|
thankYou: 'Thank you!'
|
||||||
nav:
|
nav:
|
||||||
github: 'GitHub repository'
|
github: 'GitHub repository'
|
||||||
githubRepository: 'IT-Tools GitHub repository'
|
githubRepository: 'IT-Tools GitHub repository'
|
||||||
twitter: 'Twitter account'
|
twitterX: 'X account'
|
||||||
twitterAccount: 'IT Tools Twitter account'
|
twitterXAccount: 'IT Tools X account'
|
||||||
about: 'About IT-Tools'
|
about: 'About IT-Tools'
|
||||||
aboutLabel: 'About'
|
aboutLabel: 'About'
|
||||||
darkMode: 'Dark mode'
|
darkMode: 'Dark mode'
|
||||||
|
@ -30,9 +31,9 @@ about:
|
||||||
content: >
|
content: >
|
||||||
# About IT-Tools
|
# 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).
|
IT Tools is open-source (under the GPL-3.0 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).
|
||||||
|
|
||||||
## Technologies
|
## Technologies
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ home:
|
||||||
newestTools: Nuevas herramientas
|
newestTools: Nuevas herramientas
|
||||||
favoriteTools: 'Tus herramientas favoritas'
|
favoriteTools: 'Tus herramientas favoritas'
|
||||||
allTools: 'Todas las herramientas'
|
allTools: 'Todas las herramientas'
|
||||||
|
favoritesDndToolTip: 'Arrastra y suelta para reordenar favoritos'
|
||||||
subtitle: 'Herramientas practicas para desarrolladores'
|
subtitle: 'Herramientas practicas para desarrolladores'
|
||||||
toggleMenu: 'Toggle menu'
|
toggleMenu: 'Toggle menu'
|
||||||
home: Home
|
home: Home
|
||||||
|
@ -14,13 +15,13 @@ home:
|
||||||
p1: 'Danos una estrella en'
|
p1: 'Danos una estrella en'
|
||||||
githubRepository: 'Repositorio de IT-Tools en GitHub'
|
githubRepository: 'Repositorio de IT-Tools en GitHub'
|
||||||
p2: 'o síguenos en'
|
p2: 'o síguenos en'
|
||||||
twitterAccount: 'Cuenta de twitter de IT-Tools'
|
twitterXAccount: 'Cuenta de X de IT-Tools'
|
||||||
thankYou: 'Muchas gracias!'
|
thankYou: 'Muchas gracias!'
|
||||||
nav:
|
nav:
|
||||||
github: 'Repositorio en github'
|
github: 'Repositorio en github'
|
||||||
githubRepository: 'IT-Tools GitHub repository'
|
githubRepository: 'IT-Tools GitHub repository'
|
||||||
twitter: 'Cuenta de Twitter'
|
twitterX: 'Cuenta de X'
|
||||||
twitterAccount: 'Cuenta de twitter de IT Tools'
|
twitterXAccount: 'Cuenta de X de IT Tools'
|
||||||
about: 'Sobre IT-Tools'
|
about: 'Sobre IT-Tools'
|
||||||
aboutLabel: 'Sobre'
|
aboutLabel: 'Sobre'
|
||||||
darkMode: 'Modo obscuro'
|
darkMode: 'Modo obscuro'
|
||||||
|
@ -30,9 +31,9 @@ about:
|
||||||
content: >
|
content: >
|
||||||
# Sobre IT-Tools
|
# 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).
|
IT Tools es de código abierto (under the GPL-3.0 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).
|
||||||
|
|
||||||
## Tecnologías
|
## Tecnologías
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ home:
|
||||||
newestTools: 'Les nouveaux outils'
|
newestTools: 'Les nouveaux outils'
|
||||||
favoriteTools: 'Vos outils favoris'
|
favoriteTools: 'Vos outils favoris'
|
||||||
allTools: 'Tous les outils'
|
allTools: 'Tous les outils'
|
||||||
|
favoritesDndToolTip: 'Faites glisser et déposez pour réordonner vos favoris'
|
||||||
subtitle: 'Outils pour les développeurs'
|
subtitle: 'Outils pour les développeurs'
|
||||||
toggleMenu: 'Menu'
|
toggleMenu: 'Menu'
|
||||||
home: Accueil
|
home: Accueil
|
||||||
|
@ -13,13 +14,13 @@ home:
|
||||||
p1: 'Soutenez-nous avec une star sur'
|
p1: 'Soutenez-nous avec une star sur'
|
||||||
githubRepository: "le dépôt GitHub d'IT-Tools"
|
githubRepository: "le dépôt GitHub d'IT-Tools"
|
||||||
p2: 'ou suivez-nous sur'
|
p2: 'ou suivez-nous sur'
|
||||||
twitterAccount: "le compte Twitter d'IT-Tools"
|
twitterXAccount: "le compte X d'IT-Tools"
|
||||||
thankYou: 'Merci !'
|
thankYou: 'Merci !'
|
||||||
nav:
|
nav:
|
||||||
github: 'Dépôt GitHub'
|
github: 'Dépôt GitHub'
|
||||||
githubRepository: "Dépôt GitHub d'IT-Tools"
|
githubRepository: "Dépôt GitHub d'IT-Tools"
|
||||||
twitter: 'Compte Twitter'
|
twitterX: 'Compte X'
|
||||||
twitterAccount: "Compte Twitter d'IT-Tools"
|
twitterXAccount: "Compte X d'IT-Tools"
|
||||||
about: "À propos d'IT-Tools"
|
about: "À propos d'IT-Tools"
|
||||||
aboutLabel: 'À propos'
|
aboutLabel: 'À propos'
|
||||||
darkMode: 'Mode sombre'
|
darkMode: 'Mode sombre'
|
||||||
|
@ -29,9 +30,9 @@ about:
|
||||||
content: >
|
content: >
|
||||||
# À propos de IT-Tools
|
# À 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).
|
IT Tools est open-source (sous licence GPL-3.0) 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).
|
||||||
|
|
||||||
## Technologies
|
## Technologies
|
||||||
|
|
||||||
|
|
394
locales/no.yml
Normal file
394
locales/no.yml
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
home:
|
||||||
|
categories:
|
||||||
|
newestTools: Nyeste verktøy
|
||||||
|
favoriteTools: 'Dine favoritt verktøy'
|
||||||
|
allTools: 'Alle verktøyene'
|
||||||
|
favoritesDndToolTip: 'Dra og slipp for å omordne favoritter'
|
||||||
|
subtitle: 'Nyttige verktøy for utviklere'
|
||||||
|
toggleMenu: 'Vekslemenmy'
|
||||||
|
home: Hjem
|
||||||
|
uiLib: 'UI Bib'
|
||||||
|
support: 'Støtt utviklingen av IT-Tools'
|
||||||
|
buyMeACoffee: 'Kjøp en kaffe til meg'
|
||||||
|
follow:
|
||||||
|
title: 'Liker du it-tools?'
|
||||||
|
p1: 'Gi oss en stjerne på'
|
||||||
|
githubRepository: 'IT-Tools GitHub-depotet'
|
||||||
|
p2: 'eller følg oss på'
|
||||||
|
twitterXAccount: 'IT-Tools sin X konto'
|
||||||
|
thankYou: 'Tusen takk!'
|
||||||
|
nav:
|
||||||
|
github: 'GitHub-depot'
|
||||||
|
githubRepository: 'IT-Tools GitHub-depot'
|
||||||
|
twitterX: 'X konto'
|
||||||
|
twitterXAccount: 'IT Tools X konto'
|
||||||
|
about: 'Om IT-Tools'
|
||||||
|
aboutLabel: 'Om'
|
||||||
|
darkMode: 'Mørk modus'
|
||||||
|
lightMode: 'Lys modus'
|
||||||
|
mode: 'Veksle mørk/lys modus'
|
||||||
|
about:
|
||||||
|
content: >
|
||||||
|
# Om IT-Tools
|
||||||
|
|
||||||
|
Denne vidunderlige nettsiden, laget med ❤ av [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about) , sammenstiller nyttige verktøy for utviklere og folk som jobber innen IT. Hvis du finner dette nyttig, Del det gjerne med andre som du tror kan få nytte av dette, og ikke glem å lage et bokmerke!
|
||||||
|
|
||||||
|
IT Tools er åpen kildekode (under GPL-3.0 lisensen) og gratis, og det vil det alltid være, men det koster å drifte og å fornye domenet. Hvis du ønsker å støtte arbeidet mitt, og motivere meg til å legge til flere verktøy, gjerne støtt meg ved å [sponse meg](https://www.buymeacoffee.com/cthmsst).
|
||||||
|
|
||||||
|
## Teknologier
|
||||||
|
|
||||||
|
IT Tools er laget i Vue.js (Vue 3) med Naive UI komponent bibliotektet og er hosted og kontinuerlig deployet av Vercel. Tredjeparts åpen-kildekode biblioteker er brukt i noen verktøy, du kan finne den komplette listen i [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json) filen i depoet.
|
||||||
|
|
||||||
|
## Funnet en feil? Et verktøy som mangler?
|
||||||
|
|
||||||
|
Hvis du trenger et verktøy som foreløpig ikke er tilgjengelig her, og du tenker det kan være nyttig for andre, så er du velkommen til å legge til en funksjonsforespørsel i [problem seksjonen](https://github.com/CorentinTh/it-tools/issues/new/choose) i github-depotet.
|
||||||
|
|
||||||
|
Og hvis du har funnet en feil, eller noe ikke oppfører seg som forventet, vennligst send inn en feilrapport i [problem seksjonen](https://github.com/CorentinTh/it-tools/issues/new/choose) i github-depotet.
|
||||||
|
|
||||||
|
404:
|
||||||
|
notFound: '404 ikke funnet'
|
||||||
|
sorry: 'Beklager, denne siden ser ikke ut til å eksistere'
|
||||||
|
maybe: 'Kanskje informasjonskapslene oppfører seg rart, prøvd en tvungen oppfriskning?'
|
||||||
|
backHome: 'Tilbake til start'
|
||||||
|
favoriteButton:
|
||||||
|
remove: 'Fjern fra favoritter'
|
||||||
|
add: 'Legg til favoritter'
|
||||||
|
toolCard:
|
||||||
|
new: Ny
|
||||||
|
search:
|
||||||
|
label: Søk
|
||||||
|
tools:
|
||||||
|
categories:
|
||||||
|
favorite-tools: 'Dine favoritt verktøy'
|
||||||
|
crypto: Krypto
|
||||||
|
converter: Konvertering
|
||||||
|
web: Web
|
||||||
|
images and videos: 'Bilder & Videoer'
|
||||||
|
development: Utvikling
|
||||||
|
network: Nettverk
|
||||||
|
math: Matte
|
||||||
|
measurement: Måling
|
||||||
|
text: Tekst
|
||||||
|
data: Data
|
||||||
|
|
||||||
|
password-strength-analyser:
|
||||||
|
title: Analyseverktøy for passordstyrke
|
||||||
|
description: Oppdag styrken av passordet ditt med dette kun-klient-maskin passordstyrke analyse verktøyet og se den estimerte knekketiden.
|
||||||
|
|
||||||
|
chronometer:
|
||||||
|
title: Kronometer
|
||||||
|
description: Overvåk varigheten av noe. I bunn og grunn et kronometer med enkle funksjoner.
|
||||||
|
|
||||||
|
token-generator:
|
||||||
|
title: Token generator
|
||||||
|
description: Generer en tilfeldig streng med store og/eller små bokstaver, siffer og/eller symboler.
|
||||||
|
|
||||||
|
uppercase: Store bokstaver (ABC...)
|
||||||
|
lowercase: Små bokstaver (abc...)
|
||||||
|
numbers: Siffer (123...)
|
||||||
|
symbols: Symboler (!-;...)
|
||||||
|
length: Lengde
|
||||||
|
tokenPlaceholder: 'Tokenet...'
|
||||||
|
copied: Tokenet er kopiert til utklippstavlen.
|
||||||
|
button:
|
||||||
|
copy: Kopier
|
||||||
|
refresh: Oppfrisk
|
||||||
|
percentage-calculator:
|
||||||
|
title: Prosent kalkulator
|
||||||
|
description: Beregn enkelt prosenter fra en verdi til en annen, eller fra en prosent til en verdi.
|
||||||
|
|
||||||
|
svg-placeholder-generator:
|
||||||
|
title: SVG plassholder generator
|
||||||
|
description: Generer svg bilder til å bruke som plassholder i applikasjonen din.
|
||||||
|
|
||||||
|
json-to-csv:
|
||||||
|
title: JSON til CSV
|
||||||
|
description: Konverter JSON til CSV med automatisk oppdagelse av headeren.
|
||||||
|
|
||||||
|
camera-recorder:
|
||||||
|
title: Kameraopptak
|
||||||
|
description: Ta et bilde eller spill inn en video med webkamera eller kameraet ditt.
|
||||||
|
|
||||||
|
keycode-info:
|
||||||
|
title: Tastekode info
|
||||||
|
description: Finn javascript tastekode, kode, plassering og modifikatorer av hvilken som helst tast.
|
||||||
|
|
||||||
|
emoji-picker:
|
||||||
|
title: Emoji velger
|
||||||
|
description: Klipp og lim emojis og få unicode og kode verdien av hver emoji.
|
||||||
|
|
||||||
|
color-converter:
|
||||||
|
title: Farge konverter
|
||||||
|
description: Konverter farger mellom de forskjellige formatene (hex, rgb, hsl og css navn).
|
||||||
|
|
||||||
|
bcrypt:
|
||||||
|
title: Bcrypt
|
||||||
|
description: Hash og sammenlign tekst ved hjelp av bcrypt. Bcrypt er en passord-hashings funksjon basert på Blowfish cipher.
|
||||||
|
|
||||||
|
crontab-generator:
|
||||||
|
title: Crontab generator
|
||||||
|
description: Verifiser og generer crontab og få den mennesklig leselige beskrivelsen av cron timeplanen.
|
||||||
|
|
||||||
|
http-status-codes:
|
||||||
|
title: HTTP status koder
|
||||||
|
description: Liste over alle HTTP status koder, navnet dems, og betydningen.
|
||||||
|
|
||||||
|
sql-prettify:
|
||||||
|
title: SQL forskjønning and format
|
||||||
|
description: Formater og forskjønn SQL spørringene dine (den støtter forskjellige SQL dialekter).
|
||||||
|
|
||||||
|
benchmark-builder:
|
||||||
|
title: Bygg en referansemåler
|
||||||
|
description: Sammenlign enkelt kjøretiden av oppgaver med denne enkle referansemåls byggeren.
|
||||||
|
|
||||||
|
git-memo:
|
||||||
|
title: Git jukselapp
|
||||||
|
description: Git er en desentralisert versjons håndterings programvare. Med denne jukselappen vil du få kjapp tilgang til de vanligste kommandoene.
|
||||||
|
|
||||||
|
slugify-string:
|
||||||
|
title: Slugify streng
|
||||||
|
description: Lag en trygg url, filbane eller id.
|
||||||
|
|
||||||
|
encryption:
|
||||||
|
title: Krypter / decrypter tekst
|
||||||
|
description: Krypter klartekst og dekrypter ciphertekst ved bruk av krypteringsalgoritmer som AES, TripleDES, Rabbit eller RC4.
|
||||||
|
|
||||||
|
random-port-generator:
|
||||||
|
title: Tilfeldig port generator
|
||||||
|
description: Generer tilfeldige portnumre utenfor scopet av "kjente" porter (0-1023).
|
||||||
|
|
||||||
|
yaml-prettify:
|
||||||
|
title: YAML forskjønning og formatering
|
||||||
|
description: Forskjønn YAML strengene dine til et lettlest format.
|
||||||
|
|
||||||
|
eta-calculator:
|
||||||
|
title: ETA kalkulator
|
||||||
|
description: En ETA (Estimert Tid for Ankomst) kalkulator for å anslå den sannsynelige slutt tiden for en oppgave, for eksempel, slutttiden og varigheten av en filnedlastning.
|
||||||
|
|
||||||
|
roman-numeral-converter:
|
||||||
|
title: Romertall konverter
|
||||||
|
description: Konverter romertall til tall eller konverter tall til romertall.
|
||||||
|
|
||||||
|
hmac-generator:
|
||||||
|
title: Hmac generator
|
||||||
|
description: Beregn en hash-basert meldings authentiserings kode (HMAC) ved bruk av en hemmelig nøkkel og din foretrukne hashings funksjon.
|
||||||
|
|
||||||
|
bip39-generator:
|
||||||
|
title: BIP39 nøkkelords generator
|
||||||
|
description: Generer et BIP39 nøkkelord fra en eksisterende eller tilfeldig huskesetning, eller få ut en huskesetning fra nøkkelordet.
|
||||||
|
|
||||||
|
base64-file-converter:
|
||||||
|
title: Base64 fil konverter
|
||||||
|
description: Konverter en base64 streng til fil eller en fil, bilde til en base64 representasjon.
|
||||||
|
|
||||||
|
list-converter:
|
||||||
|
title: Liste konverterer
|
||||||
|
description: Dette verktøyet kan prosessere kolonnebasert data og foreta forskjellige endringer (transposering, legge til prefix og suffix, reversere lister, sortere lister, gjøre om til små bokstaver, trunkere verdier) på hver rad.
|
||||||
|
|
||||||
|
base64-string-converter:
|
||||||
|
title: Base64 string kode/dekoder
|
||||||
|
description: Enkelt kode eller dekode en tekststreng til base64 representasjonen av strengen.
|
||||||
|
|
||||||
|
toml-to-yaml:
|
||||||
|
title: TOML til YAML
|
||||||
|
description: Parser og konverter TOML til YAML.
|
||||||
|
|
||||||
|
math-evaluator:
|
||||||
|
title: Matematikkevaluator
|
||||||
|
description: En Kalkulator for å evaluere matematiske uttrykk. Du kan bruke funksjoner som sqrt, cos, sin, abs, etc.
|
||||||
|
|
||||||
|
json-to-yaml-converter:
|
||||||
|
title: JSON til YAML konverterer
|
||||||
|
description: Enkelt konverter JSON til YAML med dette verktøyet.
|
||||||
|
|
||||||
|
url-parser:
|
||||||
|
title: URL analyse
|
||||||
|
description: Parsere en URL ned til bestanddelene (protokoll, opprinnelse, parametre, port, brukernavn-passord, ...).
|
||||||
|
|
||||||
|
iban-validator-and-parser:
|
||||||
|
title: IBAN validering og analysering
|
||||||
|
description: Valider og parser IBAN numre. Sjekk om et IBAN er gyldig og få landet, BBAN, om det er en QR-IBAN og IBAN i et vennlig format.
|
||||||
|
|
||||||
|
user-agent-parser:
|
||||||
|
title: User-agent analysering
|
||||||
|
description: Detekter og parser nettleser, motor, OS, CPU, og enhet type/modell fra en user-agent tekst streng.
|
||||||
|
|
||||||
|
numeronym-generator:
|
||||||
|
title: Numeronym generator
|
||||||
|
description: Et numeronym er et ord hvor et nummer er brukt til å lage en forkortelse. For eksempel, "i18n" er et numeronym for "internasjonalisering" hvor 18 står for antall bokstaver mellom første bokstaven i og den siste bokstaven n i ordet.
|
||||||
|
|
||||||
|
case-converter:
|
||||||
|
title: Bokstavkonvertering
|
||||||
|
description: Formater bokstavene med store eller små bokstaver, samt andre format.
|
||||||
|
|
||||||
|
html-entities:
|
||||||
|
title: HTML streng rensing
|
||||||
|
description: Rens bort eller omsvøp HTML entiteter (erstatt tegn som <,>, &, " and \' med deres HTML versjon).
|
||||||
|
|
||||||
|
json-prettify:
|
||||||
|
title: JSON forskjønning og formatering
|
||||||
|
description: Forskjønn JSON strenger til et lettlest format.
|
||||||
|
|
||||||
|
docker-run-to-docker-compose-converter:
|
||||||
|
title: Docker run til Docker compose konverter
|
||||||
|
description: Konverter "docker run" kommandoer til docker-compose filer!
|
||||||
|
|
||||||
|
mac-address-lookup:
|
||||||
|
title: MAC address oppslagsverk
|
||||||
|
description: Finn forhandler og produsent basert på MAC adressen.
|
||||||
|
|
||||||
|
mime-types:
|
||||||
|
title: MIME typer
|
||||||
|
description: Konverter MIME typer til fil utvidelser og visa-versa.
|
||||||
|
|
||||||
|
toml-to-json:
|
||||||
|
title: TOML til JSON
|
||||||
|
description: Parser og konverter TOML til JSON.
|
||||||
|
|
||||||
|
lorem-ipsum-generator:
|
||||||
|
title: Lorem ipsum generator
|
||||||
|
description: Lorem ipsum er brukt som plassholder tekst, vanligvis brukt til å demonstrere den visuelle formen av et dokument eller font-type uten å måtte ha meningsfult innhold.
|
||||||
|
|
||||||
|
qrcode-generator:
|
||||||
|
title: QR Kode generator
|
||||||
|
description: Generer og last ned en QR kode til en URL (eller ren tekst), og tilpass bakgrunns og forgrunns farger.
|
||||||
|
|
||||||
|
wifi-qrcode-generator:
|
||||||
|
title: WiFi QR Kode generator
|
||||||
|
description: Generer og last ned QR koder for rask tilkobling til wifi nettverket.
|
||||||
|
|
||||||
|
xml-formatter:
|
||||||
|
title: XML formaterer
|
||||||
|
description: Forskjønn en XML streng til et lettlest format.
|
||||||
|
|
||||||
|
temperature-converter:
|
||||||
|
title: Temperatur konverter
|
||||||
|
description: Temperatur konversjoner mellom Kelvin, Celsius, Fahrenheit, Rankine, Delisle, Newton, Réaumur, og Rømer.
|
||||||
|
|
||||||
|
chmod-calculator:
|
||||||
|
title: Chmod kalkulator
|
||||||
|
description: Beregn chmod tillatelser og kommandoer med denne chmod kalkulatoren.
|
||||||
|
|
||||||
|
rsa-key-pair-generator:
|
||||||
|
title: RSA nøkkelpar generator
|
||||||
|
description: Generer et nytt tilfeldig RSA privat og offentlig pem sertifikat nøkkel par.
|
||||||
|
|
||||||
|
html-wysiwyg-editor:
|
||||||
|
title: HTML WYSIWYG editor
|
||||||
|
description: Online, funksjonsrik WYSIWYG HTML editor som genererer kildekoden for innholdet øyeblikkelig.
|
||||||
|
|
||||||
|
yaml-to-toml:
|
||||||
|
title: YAML til TOML
|
||||||
|
description: Parser og konverter YAML til TOML.
|
||||||
|
|
||||||
|
mac-address-generator:
|
||||||
|
title: MAC adresse generator
|
||||||
|
description: Sett inn antall og prefix. MAC addressene blir generert i ønsket format
|
||||||
|
|
||||||
|
json-diff:
|
||||||
|
title: JSON diff
|
||||||
|
description: Sammenlign to JSON objekter og finn forskjellene mellom dem.
|
||||||
|
|
||||||
|
jwt-parser:
|
||||||
|
title: JWT parser
|
||||||
|
description: Parse og dekode et JSON Web Token (jwt) og vis innholdet.
|
||||||
|
|
||||||
|
date-converter:
|
||||||
|
title: Dato-tid konverter
|
||||||
|
description: Konverter dato og tid til forskjellige formater.
|
||||||
|
|
||||||
|
phone-parser-and-formatter:
|
||||||
|
title: Telefon format og parserer
|
||||||
|
description: Parser, valider og formater telefon numre. få innformasjonen om telefon nummeret, slik som landskoden, type etc.
|
||||||
|
|
||||||
|
ipv4-subnet-calculator:
|
||||||
|
title: IPv4 subnet kalkulator
|
||||||
|
description: Parser IPv4 CIDR blokker of åf all info du trenger om subnettet.
|
||||||
|
|
||||||
|
og-meta-generator:
|
||||||
|
title: Open graph meta generator
|
||||||
|
description: Generer open-graph og SoMe HTML meta tagger til nettsiden din.
|
||||||
|
|
||||||
|
ipv6-ula-generator:
|
||||||
|
title: IPv6 ULA generator
|
||||||
|
description: Generer din egen lokale, ikke-rutbare IP adresse til nettverket ditt i henhold til RFC4193.
|
||||||
|
|
||||||
|
hash-text:
|
||||||
|
title: Hash tekst
|
||||||
|
description: 'Hash en tekst streng med en av algoritmene : MD5, SHA1, SHA256, SHA224, SHA512, SHA384, SHA3 eller RIPEMD160'
|
||||||
|
|
||||||
|
json-to-toml:
|
||||||
|
title: JSON til TOML
|
||||||
|
description: Parser og konverter JSON til TOML.
|
||||||
|
|
||||||
|
device-information:
|
||||||
|
title: Enhets informasjon
|
||||||
|
description: Få informasjon om din nåværende enhet (skjermstørrelse, piksel-forhold, user agent, etc.)
|
||||||
|
|
||||||
|
pdf-signature-checker:
|
||||||
|
title: PDF signatur sjekker
|
||||||
|
description: Bekreft signaturen til en PDF fil. En signert PDF fil inneholder en eller flere signaturer som kan bli brukt til å bestemme om en fil har blitt endret etter at den var signert.
|
||||||
|
|
||||||
|
json-minify:
|
||||||
|
title: JSON minifiser
|
||||||
|
description: Minifiser og komprimer JSON ved å fjerne unødvendige mellomrom.
|
||||||
|
|
||||||
|
ulid-generator:
|
||||||
|
title: ULID generator
|
||||||
|
description: Generer tilfeldig Universell Unik Leksikografisk Sorterbar Identifikator (ULID).
|
||||||
|
|
||||||
|
string-obfuscator:
|
||||||
|
title: Streng obfuskator
|
||||||
|
description: Obfusker en streng (som en hemmelighet, en IBAN, eller et token) og gjør den delbar og identifiserbar uten å vise innholdet.
|
||||||
|
|
||||||
|
base-converter:
|
||||||
|
title: Heltalls konverter
|
||||||
|
description: Konverter et heltall mellom forskjellige baser (desimal, hexadesimal, binær, oktal, base64, etc.)
|
||||||
|
|
||||||
|
yaml-to-json-converter:
|
||||||
|
title: YAML til JSON konverter
|
||||||
|
description: Konverterl YAML til JSON.
|
||||||
|
|
||||||
|
uuid-generator:
|
||||||
|
title: UUIDs generator
|
||||||
|
description: En universell Unik Identifikator (UUID) er et 128-bit nummer, brukt til å identifisere informasjon i datasystemer.
|
||||||
|
|
||||||
|
ipv4-address-converter:
|
||||||
|
title: IPv4 adresse konverter
|
||||||
|
description: Konverter en IPv4 adresse til desimal, binær, hexadesimal, eller en IPv6 representasjon.
|
||||||
|
|
||||||
|
text-statistics:
|
||||||
|
title: Tekst statistikk
|
||||||
|
description: Få informasjonen om en tekst, antall karakterer, antall ord, størrelsen i bytes, etc.
|
||||||
|
|
||||||
|
text-to-nato-alphabet:
|
||||||
|
title: Tekst til NATO alfabetet
|
||||||
|
description: Transformer teksten til det NATO fonetiske alfabetet for muntlig gjengivelse.
|
||||||
|
|
||||||
|
basic-auth-generator:
|
||||||
|
title: Basic auth generator
|
||||||
|
description: Generer en base64 basic auth header fra et brukernavn og passord.
|
||||||
|
|
||||||
|
text-to-unicode:
|
||||||
|
title: Tekst til Unicode
|
||||||
|
description: Parser og konverter tekst til unicode og visa-versa
|
||||||
|
|
||||||
|
ipv4-range-expander:
|
||||||
|
title: IPv4 range utvider
|
||||||
|
description: Gitt en start og en slutt IPv4 adresse, kalkulerer dette verktøyet et gyldig IPv4 subnet sammen med sin CIDR notasjon.
|
||||||
|
|
||||||
|
text-diff:
|
||||||
|
title: Tekst diff
|
||||||
|
description: Sammenlign to tekster og vis forskjellen mellom dem.
|
||||||
|
|
||||||
|
otp-generator:
|
||||||
|
title: OTP kode generator
|
||||||
|
description: Generer og valider tidsbasert OTP (one time password) for multi-faktor autentisering.
|
||||||
|
|
||||||
|
url-encoder:
|
||||||
|
title: Kode/dekode URL-formaterte strenger
|
||||||
|
description: Kode tekst til URL-kodet format (også kjent som "prosent-kodet"), eller dekode fra det.
|
||||||
|
|
||||||
|
text-to-binary:
|
||||||
|
title: Tekst til ASCII binært
|
||||||
|
description: Konverter tekst til sin ASCII binære representasjon og visa-versa.
|
|
@ -3,6 +3,7 @@ home:
|
||||||
newestTools: 'Novas ferramentas'
|
newestTools: 'Novas ferramentas'
|
||||||
favoriteTools: 'Suas ferramentas favoritas'
|
favoriteTools: 'Suas ferramentas favoritas'
|
||||||
allTools: 'Todas as ferramentas'
|
allTools: 'Todas as ferramentas'
|
||||||
|
favoritesDndToolTip: 'Arraste e solte para reordenar favoritos'
|
||||||
subtitle: 'Ferraentas úteis para desenvolvedores'
|
subtitle: 'Ferraentas úteis para desenvolvedores'
|
||||||
toggleMenu: 'Menu'
|
toggleMenu: 'Menu'
|
||||||
home: 'Início'
|
home: 'Início'
|
||||||
|
@ -14,13 +15,13 @@ home:
|
||||||
p1: 'Dê uma estrela no'
|
p1: 'Dê uma estrela no'
|
||||||
githubRepository: 'repositório do IT-Tools no GitHub'
|
githubRepository: 'repositório do IT-Tools no GitHub'
|
||||||
p2: 'ou siga nossa'
|
p2: 'ou siga nossa'
|
||||||
twitterAccount: 'conta IT-Tools no Twitter'
|
twitterXAccount: 'conta IT-Tools no X'
|
||||||
thankYou: 'Obrigado !'
|
thankYou: 'Obrigado !'
|
||||||
nav:
|
nav:
|
||||||
github: 'Repositório no GitHub'
|
github: 'Repositório no GitHub'
|
||||||
githubRepository: 'repositório do IT-Tools no GitHub'
|
githubRepository: 'repositório do IT-Tools no GitHub'
|
||||||
twitter: 'Conta no Twitter'
|
twitterX: 'Conta no X'
|
||||||
twitterAccount: 'conta do IT Tools no Twitter'
|
twitterXAccount: 'conta do IT Tools no X'
|
||||||
about: 'Sobre o IT-Tools'
|
about: 'Sobre o IT-Tools'
|
||||||
aboutLabel: 'Sobre'
|
aboutLabel: 'Sobre'
|
||||||
darkMode: 'Modo Escuro'
|
darkMode: 'Modo Escuro'
|
||||||
|
@ -30,9 +31,9 @@ about:
|
||||||
content: >
|
content: >
|
||||||
# Sobre o IT-Tools
|
# 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).
|
O IT Tools é código aberto (sob a licença GPL-3.0), é 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).
|
||||||
|
|
||||||
## Tecnologias
|
## Tecnologias
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ home:
|
||||||
newestTools: Найновіші інструменти
|
newestTools: Найновіші інструменти
|
||||||
favoriteTools: 'Ваші улюблені інструменти'
|
favoriteTools: 'Ваші улюблені інструменти'
|
||||||
allTools: 'Усі інструменти'
|
allTools: 'Усі інструменти'
|
||||||
|
favoritesDndToolTip: 'Перетягніть і відпустіть, щоб змінити порядок улюблених'
|
||||||
subtitle: 'Зручні інструменти для розробників'
|
subtitle: 'Зручні інструменти для розробників'
|
||||||
toggleMenu: 'Перемикання меню'
|
toggleMenu: 'Перемикання меню'
|
||||||
home: Головна
|
home: Головна
|
||||||
|
@ -14,13 +15,13 @@ home:
|
||||||
p1: 'Додайте нам зірку на'
|
p1: 'Додайте нам зірку на'
|
||||||
githubRepository: 'GitHub-репозиторій IT-Tools'
|
githubRepository: 'GitHub-репозиторій IT-Tools'
|
||||||
p2: 'або слідкуйте за нами на'
|
p2: 'або слідкуйте за нами на'
|
||||||
twitterAccount: 'Твіттер-акаунт IT-Tools'
|
twitterXAccount: 'X-акаунт IT-Tools'
|
||||||
thankYou: 'Дякуємо!'
|
thankYou: 'Дякуємо!'
|
||||||
nav:
|
nav:
|
||||||
github: 'GitHub-репозиторій'
|
github: 'GitHub-репозиторій'
|
||||||
githubRepository: 'GitHub-репозиторій IT-Tools'
|
githubRepository: 'GitHub-репозиторій IT-Tools'
|
||||||
twitter: 'Твіттер'
|
twitterX: 'X'
|
||||||
twitterAccount: 'Твіттер-акаунт IT-Tools'
|
twitterXAccount: 'X-акаунт IT-Tools'
|
||||||
about: 'Про IT-Tools'
|
about: 'Про IT-Tools'
|
||||||
aboutLabel: 'Про нас'
|
aboutLabel: 'Про нас'
|
||||||
darkMode: 'Темний режим'
|
darkMode: 'Темний режим'
|
||||||
|
@ -30,9 +31,9 @@ about:
|
||||||
content: >
|
content: >
|
||||||
# Про IT-Tools
|
# Про 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).
|
IT Tools є відкритим програмним забезпеченням (під ліцензією GPL-3.0) і безкоштовним, і завжди буде таким, але мені коштує гроші для хостингу і продовження доменного імені. Якщо ви хочете підтримати мою роботу і підтримати мене у додаванні нових інструментів, розгляньте можливість підтримки, [спонсоруючи мене](https://www.buymeacoffee.com/cthmsst).
|
||||||
|
|
||||||
## Технології
|
## Технології
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ home:
|
||||||
newestTools: Công cụ mới nhất
|
newestTools: Công cụ mới nhất
|
||||||
favoriteTools: 'Công cụ yêu thích của bạn'
|
favoriteTools: 'Công cụ yêu thích của bạn'
|
||||||
allTools: 'Tất cả công cụ'
|
allTools: 'Tất cả công cụ'
|
||||||
|
favoritesDndToolTip: 'Kéo thả để sắp xếp lại yêu thích'
|
||||||
subtitle: 'Công cụ cho nhà phát triển.'
|
subtitle: 'Công cụ cho nhà phát triển.'
|
||||||
toggleMenu: 'Chuyển đổi menu'
|
toggleMenu: 'Chuyển đổi menu'
|
||||||
home: Trang chủ
|
home: Trang chủ
|
||||||
|
@ -14,13 +15,13 @@ home:
|
||||||
p1: 'Hãy cho chúng tôi một ngôi sao trên'
|
p1: 'Hãy cho chúng tôi một ngôi sao trên'
|
||||||
githubRepository: 'Kho GitHub IT-Tools'
|
githubRepository: 'Kho GitHub IT-Tools'
|
||||||
p2: 'hoặc theo dõi chúng tôi trên'
|
p2: 'hoặc theo dõi chúng tôi trên'
|
||||||
twitterAccount: 'Tài khoản Twitter IT-Tools'
|
twitterXAccount: 'Tài khoản X IT-Tools'
|
||||||
thankYou: 'Cảm ơn bạn!'
|
thankYou: 'Cảm ơn bạn!'
|
||||||
nav:
|
nav:
|
||||||
github: 'Kho GitHub'
|
github: 'Kho GitHub'
|
||||||
githubRepository: 'Kho GitHub IT-Tools'
|
githubRepository: 'Kho GitHub IT-Tools'
|
||||||
twitter: 'Tài khoản Twitter'
|
twitterX: 'Tài khoản X'
|
||||||
twitterAccount: 'Tài khoản Twitter IT Tools'
|
twitterXAccount: 'Tài khoản X IT Tools'
|
||||||
about: 'Về IT-Tools'
|
about: 'Về IT-Tools'
|
||||||
aboutLabel: 'Giới thiệu'
|
aboutLabel: 'Giới thiệu'
|
||||||
darkMode: 'Chế độ tối'
|
darkMode: 'Chế độ tối'
|
||||||
|
@ -30,9 +31,9 @@ about:
|
||||||
content: >
|
content: >
|
||||||
# Về IT-Tools
|
# 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).
|
IT Tools là mã nguồn mở (dưới giấy phép GPL-3.0) 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).
|
||||||
|
|
||||||
## Công nghệ
|
## Công nghệ
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ home:
|
||||||
newestTools: '最新工具'
|
newestTools: '最新工具'
|
||||||
favoriteTools: '我的收藏'
|
favoriteTools: '我的收藏'
|
||||||
allTools: '全部工具'
|
allTools: '全部工具'
|
||||||
|
favoritesDndToolTip: '拖放重新排列收藏夹'
|
||||||
subtitle: '助力开发人员和 IT 工作者'
|
subtitle: '助力开发人员和 IT 工作者'
|
||||||
toggleMenu: '切换菜单'
|
toggleMenu: '切换菜单'
|
||||||
home: '主页'
|
home: '主页'
|
||||||
|
@ -14,13 +15,13 @@ home:
|
||||||
p1: '给我们 Star'
|
p1: '给我们 Star'
|
||||||
githubRepository: 'GitHub 仓库'
|
githubRepository: 'GitHub 仓库'
|
||||||
p2: '关注我们的'
|
p2: '关注我们的'
|
||||||
twitterAccount: 'Twitter'
|
twitterXAccount: 'IT-Tools X 账号'
|
||||||
thankYou: '感谢您的支持!'
|
thankYou: '感谢您的支持!'
|
||||||
nav:
|
nav:
|
||||||
github: 'GitHub 仓库'
|
github: 'GitHub 仓库'
|
||||||
githubRepository: 'GitHub 仓库'
|
githubRepository: 'GitHub 仓库'
|
||||||
twitter: 'Twitter 账号'
|
twitterX: 'Twitter 账号'
|
||||||
twitterAccount: 'Twitter 账号'
|
twitterXAccount: 'IT-Tools X 账号'
|
||||||
about: '关于 IT-Tools'
|
about: '关于 IT-Tools'
|
||||||
aboutLabel: '关于'
|
aboutLabel: '关于'
|
||||||
darkMode: '深色模式'
|
darkMode: '深色模式'
|
||||||
|
@ -30,9 +31,9 @@ about:
|
||||||
content: >
|
content: >
|
||||||
# 关于 IT-Tools
|
# 关于 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) 进行支持。
|
IT-Tools 永久免费且开源(GPL-3.0 许可证),但需要资金用于托管和续订域名。如果您想支持我的工作,并鼓励我添加更多工具,请考虑通过 [赞助我](https://www.buymeacoffee.com/cthmsst) 进行支持。
|
||||||
|
|
||||||
## 技术
|
## 技术
|
||||||
|
|
||||||
|
|
28
package.json
28
package.json
|
@ -1,7 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "it-tools",
|
"name": "it-tools",
|
||||||
"version": "2024.5.13-a0bc346",
|
"type": "module",
|
||||||
|
"version": "2024.10.22-7ca5933",
|
||||||
|
"packageManager": "pnpm@9.11.0",
|
||||||
"description": "Collection of handy online tools for developers, with great UX. ",
|
"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": [
|
"keywords": [
|
||||||
"productivity",
|
"productivity",
|
||||||
"converter",
|
"converter",
|
||||||
|
@ -13,12 +21,6 @@
|
||||||
"developer-tools",
|
"developer-tools",
|
||||||
"developer-productivity"
|
"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": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && NODE_OPTIONS=--max_old_space_size=4096 vite build",
|
"build": "vue-tsc --noEmit && NODE_OPTIONS=--max_old_space_size=4096 vite build",
|
||||||
|
@ -37,11 +39,14 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@it-tools/bip39": "^0.0.4",
|
"@it-tools/bip39": "^0.0.4",
|
||||||
"@it-tools/oggen": "^1.3.0",
|
"@it-tools/oggen": "^1.3.0",
|
||||||
|
"@regexper/render": "^1.0.0",
|
||||||
"@sindresorhus/slugify": "^2.2.1",
|
"@sindresorhus/slugify": "^2.2.1",
|
||||||
|
"@tabler/icons-vue": "^3.20.0",
|
||||||
"@tiptap/pm": "2.1.6",
|
"@tiptap/pm": "2.1.6",
|
||||||
"@tiptap/starter-kit": "2.1.6",
|
"@tiptap/starter-kit": "2.1.6",
|
||||||
"@tiptap/vue-3": "2.0.3",
|
"@tiptap/vue-3": "2.0.3",
|
||||||
"@types/figlet": "^1.5.8",
|
"@types/figlet": "^1.5.8",
|
||||||
|
"@types/markdown-it": "^13.0.7",
|
||||||
"@vicons/material": "^0.12.0",
|
"@vicons/material": "^0.12.0",
|
||||||
"@vicons/tabler": "^0.12.0",
|
"@vicons/tabler": "^0.12.0",
|
||||||
"@vueuse/core": "^10.3.0",
|
"@vueuse/core": "^10.3.0",
|
||||||
|
@ -57,6 +62,7 @@
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"dompurify": "^3.0.6",
|
"dompurify": "^3.0.6",
|
||||||
|
"email-normalizer": "^1.0.0",
|
||||||
"emojilib": "^3.0.10",
|
"emojilib": "^3.0.10",
|
||||||
"figlet": "^1.7.0",
|
"figlet": "^1.7.0",
|
||||||
"figue": "^1.2.0",
|
"figue": "^1.2.0",
|
||||||
|
@ -64,10 +70,12 @@
|
||||||
"highlight.js": "^11.7.0",
|
"highlight.js": "^11.7.0",
|
||||||
"iarna-toml-esm": "^3.0.5",
|
"iarna-toml-esm": "^3.0.5",
|
||||||
"ibantools": "^4.3.3",
|
"ibantools": "^4.3.3",
|
||||||
|
"js-base64": "^3.7.6",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"libphonenumber-js": "^1.10.28",
|
"libphonenumber-js": "^1.10.28",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"markdown-it": "^14.0.0",
|
||||||
"marked": "^10.0.0",
|
"marked": "^10.0.0",
|
||||||
"mathjs": "^11.9.1",
|
"mathjs": "^11.9.1",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
|
@ -80,6 +88,7 @@
|
||||||
"pinia": "^2.0.34",
|
"pinia": "^2.0.34",
|
||||||
"plausible-tracker": "^0.3.8",
|
"plausible-tracker": "^0.3.8",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
|
"randexp": "^0.5.3",
|
||||||
"sql-formatter": "^13.0.0",
|
"sql-formatter": "^13.0.0",
|
||||||
"ua-parser-js": "^1.0.35",
|
"ua-parser-js": "^1.0.35",
|
||||||
"ulid": "^2.3.0",
|
"ulid": "^2.3.0",
|
||||||
|
@ -89,8 +98,11 @@
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-i18n": "^9.9.1",
|
"vue-i18n": "^9.9.1",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
|
"vue-shadow-dom": "^4.2.0",
|
||||||
"vue-tsc": "^1.8.1",
|
"vue-tsc": "^1.8.1",
|
||||||
|
"vuedraggable": "^4.1.0",
|
||||||
"xml-formatter": "^3.3.2",
|
"xml-formatter": "^3.3.2",
|
||||||
|
"xml-js": "^1.6.11",
|
||||||
"yaml": "^2.2.1"
|
"yaml": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -126,7 +138,7 @@
|
||||||
"less": "^4.1.3",
|
"less": "^4.1.3",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"typescript": "~5.2.0",
|
"typescript": "~5.2.0",
|
||||||
"unocss": "^0.57.0",
|
"unocss": "^0.65.1",
|
||||||
"unocss-preset-scrollbar": "^0.2.1",
|
"unocss-preset-scrollbar": "^0.2.1",
|
||||||
"unplugin-icons": "^0.17.0",
|
"unplugin-icons": "^0.17.0",
|
||||||
"unplugin-vue-components": "^0.25.0",
|
"unplugin-vue-components": "^0.25.0",
|
||||||
|
|
13958
pnpm-lock.yaml
generated
13958
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -48,7 +48,7 @@ const output = computed(() => transformer.value(input.value));
|
||||||
monospace
|
monospace
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>
|
<div overflow-auto>
|
||||||
<div mb-5px>
|
<div mb-5px>
|
||||||
{{ outputLabel }}
|
{{ outputLabel }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BrandGithub, BrandTwitter, InfoCircle, Moon, Sun } from '@vicons/tabler';
|
import { IconBrandGithub, IconBrandX, IconInfoCircle, IconMoon, IconSun } from '@tabler/icons-vue';
|
||||||
import { useStyleStore } from '@/stores/style.store';
|
import { useStyleStore } from '@/stores/style.store';
|
||||||
|
|
||||||
const styleStore = useStyleStore();
|
const styleStore = useStyleStore();
|
||||||
|
@ -16,32 +16,32 @@ const { isDarkTheme } = toRefs(styleStore);
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
:aria-label="$t('home.nav.githubRepository')"
|
:aria-label="$t('home.nav.githubRepository')"
|
||||||
>
|
>
|
||||||
<n-icon size="25" :component="BrandGithub" />
|
<n-icon size="25" :component="IconBrandGithub" />
|
||||||
</c-button>
|
</c-button>
|
||||||
</c-tooltip>
|
</c-tooltip>
|
||||||
|
|
||||||
<c-tooltip :tooltip="$t('home.nav.twitter')" position="bottom">
|
<c-tooltip :tooltip="$t('home.nav.twitterX')" position="bottom">
|
||||||
<c-button
|
<c-button
|
||||||
circle
|
circle
|
||||||
variant="text"
|
variant="text"
|
||||||
href="https://twitter.com/ittoolsdottech"
|
href="https://x.com/ittoolsdottech"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
:aria-label="$t('home.nav.twitterAccount')"
|
:aria-label="$t('home.nav.twitterXAccount')"
|
||||||
>
|
>
|
||||||
<n-icon size="25" :component="BrandTwitter" />
|
<n-icon size="25" :component="IconBrandX" />
|
||||||
</c-button>
|
</c-button>
|
||||||
</c-tooltip>
|
</c-tooltip>
|
||||||
|
|
||||||
<c-tooltip :tooltip="$t('home.nav.about')" position="bottom">
|
<c-tooltip :tooltip="$t('home.nav.about')" position="bottom">
|
||||||
<c-button circle variant="text" to="/about" :aria-label="$t('home.nav.aboutLabel')">
|
<c-button circle variant="text" to="/about" :aria-label="$t('home.nav.aboutLabel')">
|
||||||
<n-icon size="25" :component="InfoCircle" />
|
<n-icon size="25" :component="IconInfoCircle" />
|
||||||
</c-button>
|
</c-button>
|
||||||
</c-tooltip>
|
</c-tooltip>
|
||||||
<c-tooltip :tooltip="isDarkTheme ? $t('home.nav.lightMode') : $t('home.nav.darkMode')" position="bottom">
|
<c-tooltip :tooltip="isDarkTheme ? $t('home.nav.lightMode') : $t('home.nav.darkMode')" position="bottom">
|
||||||
<c-button circle variant="text" :aria-label="$t('home.nav.mode')" @click="() => styleStore.toggleDark()">
|
<c-button circle variant="text" :aria-label="$t('home.nav.mode')" @click="() => styleStore.toggleDark()">
|
||||||
<n-icon v-if="isDarkTheme" size="25" :component="Sun" />
|
<n-icon v-if="isDarkTheme" size="25" :component="IconSun" />
|
||||||
<n-icon v-else size="25" :component="Moon" />
|
<n-icon v-else size="25" :component="IconMoon" />
|
||||||
</c-button>
|
</c-button>
|
||||||
</c-tooltip>
|
</c-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import sqlHljs from 'highlight.js/lib/languages/sql';
|
||||||
import xmlHljs from 'highlight.js/lib/languages/xml';
|
import xmlHljs from 'highlight.js/lib/languages/xml';
|
||||||
import yamlHljs from 'highlight.js/lib/languages/yaml';
|
import yamlHljs from 'highlight.js/lib/languages/yaml';
|
||||||
import iniHljs from 'highlight.js/lib/languages/ini';
|
import iniHljs from 'highlight.js/lib/languages/ini';
|
||||||
|
import markdownHljs from 'highlight.js/lib/languages/markdown';
|
||||||
import { useCopy } from '@/composable/copy';
|
import { useCopy } from '@/composable/copy';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
|
@ -30,6 +31,7 @@ hljs.registerLanguage('html', xmlHljs);
|
||||||
hljs.registerLanguage('xml', xmlHljs);
|
hljs.registerLanguage('xml', xmlHljs);
|
||||||
hljs.registerLanguage('yaml', yamlHljs);
|
hljs.registerLanguage('yaml', yamlHljs);
|
||||||
hljs.registerLanguage('toml', iniHljs);
|
hljs.registerLanguage('toml', iniHljs);
|
||||||
|
hljs.registerLanguage('markdown', markdownHljs);
|
||||||
|
|
||||||
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
|
const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props);
|
||||||
const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) };
|
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 type { Ref } from 'vue';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export { getMimeTypeFromBase64, useDownloadFileFromBase64 };
|
export {
|
||||||
|
getMimeTypeFromBase64,
|
||||||
|
getMimeTypeFromExtension, getExtensionFromMimeType,
|
||||||
|
useDownloadFileFromBase64, useDownloadFileFromBase64Refs,
|
||||||
|
previewImageFromBase64,
|
||||||
|
};
|
||||||
|
|
||||||
const commonMimeTypesSignatures = {
|
const commonMimeTypesSignatures = {
|
||||||
'JVBERi0': 'application/pdf',
|
'JVBERi0': 'application/pdf',
|
||||||
|
@ -36,30 +41,78 @@ function getFileExtensionFromMimeType({
|
||||||
defaultExtension?: string
|
defaultExtension?: string
|
||||||
}) {
|
}) {
|
||||||
if (mimeType) {
|
if (mimeType) {
|
||||||
return getExtensionFromMime(mimeType) ?? defaultExtension;
|
return getExtensionFromMimeType(mimeType) ?? defaultExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultExtension;
|
return defaultExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useDownloadFileFromBase64({ source, filename }: { source: Ref<string>; filename?: string }) {
|
function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }:
|
||||||
return {
|
{ sourceValue: string; filename?: string; extension?: string; fileMimeType?: string }) {
|
||||||
download() {
|
if (sourceValue === '') {
|
||||||
if (source.value === '') {
|
|
||||||
throw new Error('Base64 string is empty');
|
throw new Error('Base64 string is empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { mimeType } = getMimeTypeFromBase64({ base64String: source.value });
|
const defaultExtension = extension ?? 'txt';
|
||||||
const base64String = mimeType
|
const { mimeType } = getMimeTypeFromBase64({ base64String: sourceValue });
|
||||||
? source.value
|
let base64String = sourceValue;
|
||||||
: `data:text/plain;base64,${source.value}`;
|
if (!mimeType) {
|
||||||
|
const targetMimeType = fileMimeType ?? getMimeTypeFromExtension(defaultExtension);
|
||||||
|
base64String = `data:${targetMimeType};base64,${sourceValue}`;
|
||||||
|
}
|
||||||
|
|
||||||
const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`;
|
const cleanExtension = extension ?? getFileExtensionFromMimeType(
|
||||||
|
{ mimeType, defaultExtension });
|
||||||
|
let cleanFileName = filename ?? `file.${cleanExtension}`;
|
||||||
|
if (extension && !cleanFileName.endsWith(`.${extension}`)) {
|
||||||
|
cleanFileName = `${cleanFileName}.${cleanExtension}`;
|
||||||
|
}
|
||||||
|
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = base64String;
|
a.href = base64String;
|
||||||
a.download = cleanFileName;
|
a.download = cleanFileName;
|
||||||
a.click();
|
a.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function useDownloadFileFromBase64(
|
||||||
|
{ source, filename, extension, fileMimeType }:
|
||||||
|
{ source: Ref<string>; filename?: string; extension?: string; fileMimeType?: string }) {
|
||||||
|
return {
|
||||||
|
download() {
|
||||||
|
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 { useRouteQuery } from '@vueuse/router';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { useStorage } from '@vueuse/core';
|
||||||
|
|
||||||
export { useQueryParam };
|
export { useQueryParam, useQueryParamOrStorage };
|
||||||
|
|
||||||
const transformers = {
|
const transformers = {
|
||||||
number: {
|
number: {
|
||||||
|
@ -16,6 +17,12 @@ const transformers = {
|
||||||
fromQuery: (value: string) => value.toLowerCase() === 'true',
|
fromQuery: (value: string) => value.toLowerCase() === 'true',
|
||||||
toQuery: (value: boolean) => (value ? 'true' : 'false'),
|
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 }) {
|
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';
|
import { type Ref, reactive, watch } from 'vue';
|
||||||
|
|
||||||
type ValidatorReturnType = unknown;
|
type ValidatorReturnType = unknown;
|
||||||
|
type GetErrorMessageReturnType = string;
|
||||||
|
|
||||||
export interface UseValidationRule<T> {
|
export interface UseValidationRule<T> {
|
||||||
validator: (value: T) => ValidatorReturnType
|
validator: (value: T) => ValidatorReturnType
|
||||||
|
getErrorMessage?: (value: T) => GetErrorMessageReturnType
|
||||||
message: string
|
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 {
|
export interface ValidationAttrs {
|
||||||
feedback: string
|
feedback: string
|
||||||
validationStatus: string | undefined
|
validationStatus: string | undefined
|
||||||
|
@ -61,7 +72,13 @@ export function useValidation<T>({
|
||||||
|
|
||||||
for (const rule of get(rules)) {
|
for (const rule of get(rules)) {
|
||||||
if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
|
if (isFalsyOrHasThrown(() => rule.validator(source.value))) {
|
||||||
|
if (rule.getErrorMessage) {
|
||||||
|
const getErrorMessage = rule.getErrorMessage;
|
||||||
|
state.message = rule.message.replace('{0}', getErrorMessageOrThrown(() => getErrorMessage(source.value)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
state.message = rule.message;
|
state.message = rule.message;
|
||||||
|
}
|
||||||
state.status = 'error';
|
state.status = 'error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,12 @@ export const config = figue({
|
||||||
default: false,
|
default: false,
|
||||||
env: 'VITE_SHOW_BANNER',
|
env: 'VITE_SHOW_BANNER',
|
||||||
},
|
},
|
||||||
|
showSponsorBanner: {
|
||||||
|
doc: 'Show the sponsor banner',
|
||||||
|
format: 'boolean',
|
||||||
|
default: false,
|
||||||
|
env: 'VITE_SHOW_SPONSOR_BANNER',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.loadEnv({
|
.loadEnv({
|
||||||
...import.meta.env,
|
...import.meta.env,
|
||||||
|
|
|
@ -81,7 +81,7 @@ const tools = computed<ToolCategory[]>(() => [
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
© {{ new Date().getFullYear() }}
|
© {{ 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
|
Corentin Thomasset
|
||||||
</c-link>
|
</c-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,7 +40,7 @@ const toolDescription = computed<string>(() => t(`tools.${i18nKey.value}.descrip
|
||||||
</n-h1>
|
</n-h1>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<FavoriteButton :tool="{ name: route.meta.name } as Tool" />
|
<FavoriteButton :tool="{ name: route.meta.name, path: route.path } as Tool" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { createPinia } from 'pinia';
|
||||||
import { createHead } from '@vueuse/head';
|
import { createHead } from '@vueuse/head';
|
||||||
|
|
||||||
import { registerSW } from 'virtual:pwa-register';
|
import { registerSW } from 'virtual:pwa-register';
|
||||||
|
import shadow from 'vue-shadow-dom';
|
||||||
import { plausible } from './plugins/plausible.plugin';
|
import { plausible } from './plugins/plausible.plugin';
|
||||||
|
|
||||||
import 'virtual:uno.css';
|
import 'virtual:uno.css';
|
||||||
|
@ -23,5 +24,6 @@ app.use(i18nPlugin);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(naive);
|
app.use(naive);
|
||||||
app.use(plausible);
|
app.use(plausible);
|
||||||
|
app.use(shadow);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
|
@ -6,6 +6,7 @@ const localesLong: Record<string, string> = {
|
||||||
de: 'Deutsch',
|
de: 'Deutsch',
|
||||||
es: 'Español',
|
es: 'Español',
|
||||||
fr: 'Français',
|
fr: 'Français',
|
||||||
|
no: 'Norwegian',
|
||||||
pt: 'Português',
|
pt: 'Português',
|
||||||
ru: 'Русский',
|
ru: 'Русский',
|
||||||
uk: 'Українська',
|
uk: 'Українська',
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Heart } from '@vicons/tabler';
|
import { IconDragDrop, IconHeart } from '@tabler/icons-vue';
|
||||||
import { useHead } from '@vueuse/head';
|
import { useHead } from '@vueuse/head';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import Draggable from 'vuedraggable';
|
||||||
import ColoredCard from '../components/ColoredCard.vue';
|
import ColoredCard from '../components/ColoredCard.vue';
|
||||||
import ToolCard from '../components/ToolCard.vue';
|
import ToolCard from '../components/ToolCard.vue';
|
||||||
import { useToolStore } from '@/tools/tools.store';
|
import { useToolStore } from '@/tools/tools.store';
|
||||||
|
@ -10,13 +12,20 @@ const toolStore = useToolStore();
|
||||||
|
|
||||||
useHead({ title: 'IT Tools - Handy online tools for developers' });
|
useHead({ title: 'IT Tools - Handy online tools for developers' });
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const favoriteTools = computed(() => toolStore.favoriteTools);
|
||||||
|
|
||||||
|
// Update favorite tools order when drag is finished
|
||||||
|
function onUpdateFavoriteTools() {
|
||||||
|
toolStore.updateFavoriteTools(favoriteTools.value); // Update the store with the new order
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="pt-50px">
|
<div class="pt-50px">
|
||||||
<div class="grid-wrapper">
|
<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">
|
<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 :title="$t('home.follow.title')" :icon="Heart">
|
<ColoredCard v-if="config.showBanner" :title="$t('home.follow.title')" :icon="IconHeart">
|
||||||
{{ $t('home.follow.p1') }}
|
{{ $t('home.follow.p1') }}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/CorentinTh/it-tools"
|
href="https://github.com/CorentinTh/it-tools"
|
||||||
|
@ -26,29 +35,40 @@ const { t } = useI18n();
|
||||||
>GitHub</a>
|
>GitHub</a>
|
||||||
{{ $t('home.follow.p2') }}
|
{{ $t('home.follow.p2') }}
|
||||||
<a
|
<a
|
||||||
href="https://twitter.com/ittoolsdottech"
|
href="https://x.com/ittoolsdottech"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
:aria-label="$t('home.follow.twitterAccount')"
|
:aria-label="$t('home.follow.twitterXAccount')"
|
||||||
>Twitter</a>.
|
>X</a>.
|
||||||
{{ $t('home.follow.thankYou') }}
|
{{ $t('home.follow.thankYou') }}
|
||||||
<n-icon :component="Heart" />
|
<n-icon :component="IconHeart" />
|
||||||
</ColoredCard>
|
</ColoredCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="height">
|
<transition name="height">
|
||||||
<div v-if="toolStore.favoriteTools.length > 0">
|
<div v-if="toolStore.favoriteTools.length > 0">
|
||||||
<h3 class="mb-5px mt-25px font-500 text-neutral-400">
|
<h3 class="mb-5px mt-25px text-neutral-400 font-500">
|
||||||
{{ $t('home.categories.favoriteTools') }}
|
{{ $t('home.categories.favoriteTools') }}
|
||||||
|
<c-tooltip :tooltip="$t('home.categories.favoritesDndToolTip')">
|
||||||
|
<n-icon :component="IconDragDrop" size="18" />
|
||||||
|
</c-tooltip>
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4">
|
<Draggable
|
||||||
<ToolCard v-for="tool in toolStore.favoriteTools" :key="tool.name" :tool="tool" />
|
:list="favoriteTools"
|
||||||
</div>
|
class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4"
|
||||||
|
ghost-class="ghost-favorites-draggable"
|
||||||
|
item-key="name"
|
||||||
|
@end="onUpdateFavoriteTools"
|
||||||
|
>
|
||||||
|
<template #item="{ element: tool }">
|
||||||
|
<ToolCard :tool="tool" />
|
||||||
|
</template>
|
||||||
|
</Draggable>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<div v-if="toolStore.newTools.length > 0">
|
<div v-if="toolStore.newTools.length > 0">
|
||||||
<h3 class="mb-5px mt-25px font-500 text-neutral-400">
|
<h3 class="mb-5px mt-25px text-neutral-400 font-500">
|
||||||
{{ t('home.categories.newestTools') }}
|
{{ t('home.categories.newestTools') }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4">
|
<div class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||||
|
@ -56,7 +76,7 @@ const { t } = useI18n();
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="mb-5px mt-25px font-500 text-neutral-400">
|
<h3 class="mb-5px mt-25px text-neutral-400 font-500">
|
||||||
{{ $t('home.categories.allTools') }}
|
{{ $t('home.categories.allTools') }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4">
|
<div class="grid grid-cols-1 gap-12px lg:grid-cols-3 md:grid-cols-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||||
|
@ -81,4 +101,24 @@ const { t } = useI18n();
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ghost-favorites-draggable {
|
||||||
|
opacity: 0.4;
|
||||||
|
background-color: #ccc;
|
||||||
|
border: 2px dashed #666;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||||
|
transform: scale(1.1);
|
||||||
|
animation: ghost-favorites-draggable-animation 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ghost-favorites-draggable-animation {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.4;
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,12 +2,19 @@
|
||||||
import { useBase64 } from '@vueuse/core';
|
import { useBase64 } from '@vueuse/core';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
import { useCopy } from '@/composable/copy';
|
import { useCopy } from '@/composable/copy';
|
||||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
|
import { getExtensionFromMimeType, getMimeTypeFromBase64, previewImageFromBase64, useDownloadFileFromBase64Refs } from '@/composable/downloadBase64';
|
||||||
import { useValidation } from '@/composable/validation';
|
import { useValidation } from '@/composable/validation';
|
||||||
import { isValidBase64 } from '@/utils/base64';
|
import { isValidBase64 } from '@/utils/base64';
|
||||||
|
|
||||||
|
const fileName = ref('file');
|
||||||
|
const fileExtension = ref('');
|
||||||
const base64Input = ref('');
|
const base64Input = ref('');
|
||||||
const { download } = useDownloadFileFromBase64({ source: base64Input });
|
const { download } = useDownloadFileFromBase64Refs(
|
||||||
|
{
|
||||||
|
source: base64Input,
|
||||||
|
filename: fileName,
|
||||||
|
extension: fileExtension,
|
||||||
|
});
|
||||||
const base64InputValidation = useValidation({
|
const base64InputValidation = useValidation({
|
||||||
source: base64Input,
|
source: base64Input,
|
||||||
rules: [
|
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() {
|
function downloadFile() {
|
||||||
if (!base64InputValidation.isValid) {
|
if (!base64InputValidation.isValid) {
|
||||||
return;
|
return;
|
||||||
|
@ -44,6 +80,24 @@ async function onUpload(file: File) {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<c-card title="Base64 to file">
|
<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
|
<c-input-text
|
||||||
v-model:value="base64Input"
|
v-model:value="base64Input"
|
||||||
multiline
|
multiline
|
||||||
|
@ -53,7 +107,14 @@ async function onUpload(file: File) {
|
||||||
mb-2
|
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()">
|
<c-button :disabled="base64Input === '' || !base64InputValidation.isValid" @click="downloadFile()">
|
||||||
Download file
|
Download file
|
||||||
</c-button>
|
</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 _ from 'lodash';
|
||||||
import type { EmojiInfo } from './emoji.types';
|
import type { EmojiInfo } from './emoji.types';
|
||||||
import { useFuzzySearch } from '@/composable/fuzzySearch';
|
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 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;
|
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 }))
|
.map((emojiInfos, group) => ({ group, emojiInfos }))
|
||||||
.value();
|
.value();
|
||||||
|
|
||||||
const searchQuery = ref('');
|
const searchQuery = useDebouncedRef('', 500);
|
||||||
|
|
||||||
const { searchResult } = useFuzzySearch({
|
const { searchResult } = useFuzzySearch({
|
||||||
search: searchQuery,
|
search: searchQuery,
|
||||||
|
|
|
@ -84,8 +84,8 @@ const items: MenuItem[] = [
|
||||||
type: 'button',
|
type: 'button',
|
||||||
icon: H3,
|
icon: H3,
|
||||||
title: 'Heading 3',
|
title: 'Heading 3',
|
||||||
action: () => editor.value.chain().focus().toggleHeading({ level: 4 }).run(),
|
action: () => editor.value.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||||
isActive: () => editor.value.isActive('heading', { level: 4 }),
|
isActive: () => editor.value.isActive('heading', { level: 3 }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'button',
|
type: 'button',
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import { tool as base64FileConverter } from './base64-file-converter';
|
import { tool as base64FileConverter } from './base64-file-converter';
|
||||||
import { tool as base64StringConverter } from './base64-string-converter';
|
import { tool as base64StringConverter } from './base64-string-converter';
|
||||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
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 asciiTextDrawer } from './ascii-text-drawer';
|
||||||
|
|
||||||
import { tool as textToUnicode } from './text-to-unicode';
|
import { tool as textToUnicode } from './text-to-unicode';
|
||||||
import { tool as safelinkDecoder } from './safelink-decoder';
|
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 pdfSignatureChecker } from './pdf-signature-checker';
|
||||||
import { tool as numeronymGenerator } from './numeronym-generator';
|
import { tool as numeronymGenerator } from './numeronym-generator';
|
||||||
import { tool as macAddressGenerator } from './mac-address-generator';
|
import { tool as macAddressGenerator } from './mac-address-generator';
|
||||||
|
@ -107,6 +113,9 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
listConverter,
|
listConverter,
|
||||||
tomlToJson,
|
tomlToJson,
|
||||||
tomlToYaml,
|
tomlToYaml,
|
||||||
|
xmlToJson,
|
||||||
|
jsonToXml,
|
||||||
|
markdownToHtml,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -148,6 +157,9 @@ export const toolsByCategory: ToolCategory[] = [
|
||||||
dockerRunToDockerComposeConverter,
|
dockerRunToDockerComposeConverter,
|
||||||
xmlFormatter,
|
xmlFormatter,
|
||||||
yamlViewer,
|
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 }}
|
{{ section.title }}
|
||||||
</th>
|
</th>
|
||||||
<tr v-for="{ claim, claimDescription, friendlyValue, value } in decodedJWT[section.key]" :key="claim + value">
|
<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>
|
<span font-bold>
|
||||||
{{ claim }}
|
{{ claim }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -47,7 +47,7 @@ const validation = useValidation({
|
||||||
({{ claimDescription }})
|
({{ claimDescription }})
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td style="word-wrap: break-word;word-break: break-all;">
|
||||||
<span>{{ value }}</span>
|
<span>{{ value }}</span>
|
||||||
<span v-if="friendlyValue" ml-2 op-70>
|
<span v-if="friendlyValue" ml-2 op-70>
|
||||||
({{ friendlyValue }})
|
({{ friendlyValue }})
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { generateLoremIpsum } from './lorem-ipsum-generator.service';
|
import { generateLoremIpsum } from './lorem-ipsum-generator.service';
|
||||||
import { useCopy } from '@/composable/copy';
|
import { useCopy } from '@/composable/copy';
|
||||||
import { randIntFromInterval } from '@/utils/random';
|
import { randIntFromInterval } from '@/utils/random';
|
||||||
|
import { computedRefreshable } from '@/composable/computedRefreshable';
|
||||||
|
|
||||||
const paragraphs = ref(1);
|
const paragraphs = ref(1);
|
||||||
const sentences = ref([3, 8]);
|
const sentences = ref([3, 8]);
|
||||||
|
@ -9,7 +10,7 @@ const words = ref([8, 15]);
|
||||||
const startWithLoremIpsum = ref(true);
|
const startWithLoremIpsum = ref(true);
|
||||||
const asHTML = ref(false);
|
const asHTML = ref(false);
|
||||||
|
|
||||||
const loremIpsumText = computed(() =>
|
const [loremIpsumText, refreshLoremIpsum] = computedRefreshable(() =>
|
||||||
generateLoremIpsum({
|
generateLoremIpsum({
|
||||||
paragraphCount: paragraphs.value,
|
paragraphCount: paragraphs.value,
|
||||||
asHTML: asHTML.value,
|
asHTML: asHTML.value,
|
||||||
|
@ -18,6 +19,7 @@ const loremIpsumText = computed(() =>
|
||||||
startWithLoremIpsum: startWithLoremIpsum.value,
|
startWithLoremIpsum: startWithLoremIpsum.value,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to the clipboard' });
|
const { copy } = useCopy({ source: loremIpsumText, text: 'Lorem ipsum copied to the clipboard' });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -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" />
|
<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()">
|
<c-button autofocus @click="copy()">
|
||||||
Copy
|
Copy
|
||||||
</c-button>
|
</c-button>
|
||||||
|
<c-button @click="refreshLoremIpsum">
|
||||||
|
Refresh
|
||||||
|
</c-button>
|
||||||
</div>
|
</div>
|
||||||
</c-card>
|
</c-card>
|
||||||
</template>
|
</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 ({
|
return ({
|
||||||
...tool,
|
...tool,
|
||||||
|
path: tool.path,
|
||||||
name: t(`tools.${toolI18nKey}.title`, tool.name),
|
name: t(`tools.${toolI18nKey}.title`, tool.name),
|
||||||
description: t(`tools.${toolI18nKey}.description`, tool.description),
|
description: t(`tools.${toolI18nKey}.description`, tool.description),
|
||||||
category: t(`tools.categories.${tool.category.toLowerCase()}`, tool.category),
|
category: t(`tools.categories.${tool.category.toLowerCase()}`, tool.category),
|
||||||
|
@ -23,8 +24,9 @@ export const useToolStore = defineStore('tools', () => {
|
||||||
const toolsByCategory = computed<ToolCategory[]>(() => {
|
const toolsByCategory = computed<ToolCategory[]>(() => {
|
||||||
return _.chain(tools.value)
|
return _.chain(tools.value)
|
||||||
.groupBy('category')
|
.groupBy('category')
|
||||||
.map((components, name) => ({
|
.map((components, name, path) => ({
|
||||||
name,
|
name,
|
||||||
|
path,
|
||||||
components,
|
components,
|
||||||
}))
|
}))
|
||||||
.value();
|
.value();
|
||||||
|
@ -32,7 +34,7 @@ export const useToolStore = defineStore('tools', () => {
|
||||||
|
|
||||||
const favoriteTools = computed(() => {
|
const favoriteTools = computed(() => {
|
||||||
return favoriteToolsName.value
|
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
|
.filter(Boolean) as ToolWithCategory[]; // cast because .filter(Boolean) does not remove undefined from type
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -43,15 +45,23 @@ export const useToolStore = defineStore('tools', () => {
|
||||||
newTools: computed(() => tools.value.filter(({ isNew }) => isNew)),
|
newTools: computed(() => tools.value.filter(({ isNew }) => isNew)),
|
||||||
|
|
||||||
addToolToFavorites({ tool }: { tool: MaybeRef<Tool> }) {
|
addToolToFavorites({ tool }: { tool: MaybeRef<Tool> }) {
|
||||||
favoriteToolsName.value.push(get(tool).name);
|
const toolPath = get(tool).path;
|
||||||
|
if (toolPath) {
|
||||||
|
favoriteToolsName.value.push(toolPath);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeToolFromFavorites({ tool }: { tool: MaybeRef<Tool> }) {
|
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> }) {
|
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);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateFavoriteTools(newOrder: ToolWithCategory[]) {
|
||||||
|
favoriteToolsName.value = newOrder.map(tool => 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>
|
|
@ -28,4 +28,53 @@ test.describe('Tool - Yaml to json', () => {
|
||||||
`.trim(),
|
`.trim(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Yaml is parsed with merge key and output correct json', async ({ page }) => {
|
||||||
|
await page.getByTestId('input').fill(`
|
||||||
|
default: &default
|
||||||
|
name: ''
|
||||||
|
age: 0
|
||||||
|
|
||||||
|
person:
|
||||||
|
*default
|
||||||
|
|
||||||
|
persons:
|
||||||
|
- <<: *default
|
||||||
|
age: 1
|
||||||
|
- <<: *default
|
||||||
|
name: John
|
||||||
|
- { age: 3, <<: *default }
|
||||||
|
|
||||||
|
`);
|
||||||
|
|
||||||
|
const generatedJson = await page.getByTestId('area-content').innerText();
|
||||||
|
|
||||||
|
expect(generatedJson.trim()).toEqual(
|
||||||
|
`
|
||||||
|
{
|
||||||
|
"default": {
|
||||||
|
"name": "",
|
||||||
|
"age": 0
|
||||||
|
},
|
||||||
|
"person": {
|
||||||
|
"name": "",
|
||||||
|
"age": 0
|
||||||
|
},
|
||||||
|
"persons": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"age": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "John",
|
||||||
|
"age": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 3,
|
||||||
|
"name": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`.trim(),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { withDefaultOnError } from '@/utils/defaults';
|
||||||
|
|
||||||
function transformer(value: string) {
|
function transformer(value: string) {
|
||||||
return withDefaultOnError(() => {
|
return withDefaultOnError(() => {
|
||||||
const obj = parseYaml(value);
|
const obj = parseYaml(value, { merge: true });
|
||||||
return obj ? JSON.stringify(obj, null, 3) : '';
|
return obj ? JSON.stringify(obj, null, 3) : '';
|
||||||
}, '');
|
}, '');
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,8 @@ describe('base64 utils', () => {
|
||||||
|
|
||||||
it('should throw for incorrect base64 string', () => {
|
it('should throw for incorrect base64 string', () => {
|
||||||
expect(() => base64ToText('a')).to.throw('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');
|
expect(() => base64ToText('é')).to.throw('Incorrect base64 string');
|
||||||
// missing final '='
|
// missing final '='
|
||||||
expect(() => base64ToText('bG9yZW0gaXBzdW0')).to.throw('Incorrect base64 string');
|
expect(() => base64ToText('bG9yZW0gaXBzdW0')).to.throw('Incorrect base64 string');
|
||||||
|
@ -56,17 +57,17 @@ describe('base64 utils', () => {
|
||||||
|
|
||||||
it('should return false for incorrect base64 string', () => {
|
it('should return false for incorrect base64 string', () => {
|
||||||
expect(isValidBase64('a')).to.eql(false);
|
expect(isValidBase64('a')).to.eql(false);
|
||||||
expect(isValidBase64(' ')).to.eql(false);
|
|
||||||
expect(isValidBase64('é')).to.eql(false);
|
expect(isValidBase64('é')).to.eql(false);
|
||||||
expect(isValidBase64('data:text/plain;notbase64,YQ==')).to.eql(false);
|
expect(isValidBase64('data:text/plain;notbase64,YQ==')).to.eql(false);
|
||||||
// missing final '='
|
// missing final '='
|
||||||
expect(isValidBase64('bG9yZW0gaXBzdW0')).to.eql(false);
|
expect(isValidBase64('bG9yZW0gaXBzdW0')).to.eql(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false for untrimmed correct base64 string', () => {
|
it('should return true for untrimmed correct base64 string', () => {
|
||||||
expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(false);
|
expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(true);
|
||||||
expect(isValidBase64(' LTE=')).to.eql(false);
|
expect(isValidBase64(' LTE=')).to.eql(true);
|
||||||
expect(isValidBase64(' YQ== ')).to.eql(false);
|
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 };
|
export { textToBase64, base64ToText, isValidBase64, removePotentialDataAndMimePrefix };
|
||||||
|
|
||||||
function textToBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
|
function textToBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
|
||||||
const encoded = window.btoa(str);
|
const encoded = Base64.encode(str);
|
||||||
return makeUrlSafe ? makeUriSafe(encoded) : encoded;
|
return makeUrlSafe ? makeUriSafe(encoded) : encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +18,7 @@ function base64ToText(str: string, { makeUrlSafe = false }: { makeUrlSafe?: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return window.atob(cleanStr);
|
return Base64.decode(cleanStr);
|
||||||
}
|
}
|
||||||
catch (_) {
|
catch (_) {
|
||||||
throw new Error('Incorrect base64 string');
|
throw new Error('Incorrect base64 string');
|
||||||
|
@ -34,10 +36,11 @@ function isValidBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boo
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const reEncodedBase64 = Base64.fromUint8Array(Base64.toUint8Array(cleanStr));
|
||||||
if (makeUrlSafe) {
|
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) {
|
catch (err) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import { presetScrollbar } from 'unocss-preset-scrollbar';
|
import { presetScrollbar } from 'unocss-preset-scrollbar';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
presets: [presetUno(), presetAttributify(), presetTypography(), presetScrollbar()],
|
presets: [presetUno(), presetAttributify({ ignoreAttributes: ['size'] }), presetTypography(), presetScrollbar()],
|
||||||
transformers: [transformerDirectives(), transformerVariantGroup()],
|
transformers: [transformerDirectives(), transformerVariantGroup()],
|
||||||
theme: {
|
theme: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue