From 9eac9cb2a9d23619b0d3c044f1bc1385aab427e4 Mon Sep 17 00:00:00 2001 From: sharevb Date: Mon, 22 Apr 2024 08:45:51 +0200 Subject: [PATCH 01/41] fix(integer base converter): support bigint (#872) --- .../integer-base-converter.model.test.ts | 3 +++ .../integer-base-converter.model.ts | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tools/integer-base-converter/integer-base-converter.model.test.ts b/src/tools/integer-base-converter/integer-base-converter.model.test.ts index d0387b64..c7d7db79 100644 --- a/src/tools/integer-base-converter/integer-base-converter.model.test.ts +++ b/src/tools/integer-base-converter/integer-base-converter.model.test.ts @@ -11,6 +11,9 @@ describe('integer-base-converter', () => { expect(convertBase({ value: '10100101', fromBase: 2, toBase: 16 })).toEqual('a5'); expect(convertBase({ value: '192654', fromBase: 10, toBase: 8 })).toEqual('570216'); expect(convertBase({ value: 'zz', fromBase: 64, toBase: 10 })).toEqual('2275'); + expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 10 })).toEqual('42540766411283223938465490632011909384'); + expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 16 })).toEqual('20010db8000085a300000000ac1f8908'); + expect(convertBase({ value: '20010db8000085a300000000ac1f8908', fromBase: 16, toBase: 10 })).toEqual('42540766411283223938465490632011909384'); }); }); }); diff --git a/src/tools/integer-base-converter/integer-base-converter.model.ts b/src/tools/integer-base-converter/integer-base-converter.model.ts index b4470e57..da0fe77f 100644 --- a/src/tools/integer-base-converter/integer-base-converter.model.ts +++ b/src/tools/integer-base-converter/integer-base-converter.model.ts @@ -5,16 +5,16 @@ export function convertBase({ value, fromBase, toBase }: { value: string; fromBa let decValue = value .split('') .reverse() - .reduce((carry: number, digit: string, index: number) => { + .reduce((carry: bigint, digit: string, index: number) => { if (!fromRange.includes(digit)) { throw new Error(`Invalid digit "${digit}" for base ${fromBase}.`); } - return (carry += fromRange.indexOf(digit) * fromBase ** index); - }, 0); + return (carry += BigInt(fromRange.indexOf(digit)) * BigInt(fromBase) ** BigInt(index)); + }, 0n); let newValue = ''; while (decValue > 0) { - newValue = toRange[decValue % toBase] + newValue; - decValue = (decValue - (decValue % toBase)) / toBase; + newValue = toRange[Number(decValue % BigInt(toBase))] + newValue; + decValue = (decValue - (decValue % BigInt(toBase))) / BigInt(toBase); } return newValue || '0'; } From cb5b462e119d4231b5c07b4ef0ca24ec84ceee8b Mon Sep 17 00:00:00 2001 From: Pavel Gordon Date: Mon, 29 Apr 2024 11:58:08 +0200 Subject: [PATCH 02/41] fix(url-encoder, validation): typo in validation of url-encoder.vue #1024 --- src/tools/url-encoder/url-encoder.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/url-encoder/url-encoder.vue b/src/tools/url-encoder/url-encoder.vue index c43f8193..19025190 100644 --- a/src/tools/url-encoder/url-encoder.vue +++ b/src/tools/url-encoder/url-encoder.vue @@ -23,7 +23,7 @@ const decodeInput = ref('Hello%20world%20%3A)'); const decodeOutput = computed(() => withDefaultOnError(() => decodeURIComponent(decodeInput.value), '')); const decodeValidation = useValidation({ - source: encodeInput, + source: decodeInput, rules: [ { validator: value => isNotThrowing(() => decodeURIComponent(value)), From 221ddfa75c5731d7a5dc1f0b03663ba4fd9e7965 Mon Sep 17 00:00:00 2001 From: Christopher Conley Date: Sun, 5 May 2024 12:49:31 -0400 Subject: [PATCH 03/41] fix(language): English language cleanup (#1036) Fix possessive vs. contraction form of "it," clarification, and some general cleanup --- locales/en.yml | 90 +++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 50d48af9..d09d435a 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -7,7 +7,7 @@ home: toggleMenu: 'Toggle menu' home: Home uiLib: 'UI Lib' - support: 'Support IT Tools development' + support: 'Support IT-Tools development' buyMeACoffee: 'Buy me a coffee' follow: title: 'You like it-tools?' @@ -15,7 +15,7 @@ home: githubRepository: 'IT-Tools GitHub repository' p2: 'or follow us on' twitterAccount: 'IT-Tools Twitter account' - thankYou: 'Thank you !' + thankYou: 'Thank you!' nav: github: 'GitHub repository' githubRepository: 'IT-Tools GitHub repository' @@ -72,7 +72,7 @@ tools: password-strength-analyser: title: Password strength analyser - description: Discover the strength of your password with this client side only password strength analyser and crack time estimation tool. + description: Discover the strength of your password with this client-side-only password strength analyser and crack time estimation tool. chronometer: title: Chronometer @@ -98,7 +98,7 @@ tools: svg-placeholder-generator: title: SVG placeholder generator - description: Generate svg images to use as placeholder in your applications. + description: Generate svg images to use as a placeholder in your applications. json-to-csv: title: JSON to CSV @@ -126,11 +126,11 @@ tools: crontab-generator: title: Crontab generator - description: Validate and generate crontab and get the human readable description of the cron schedule. + description: Validate and generate crontab and get the human-readable description of the cron schedule. http-status-codes: title: HTTP status codes - description: The list of all HTTP status codes their name and their meaning. + description: The list of all HTTP status codes, their name, and their meaning. sql-prettify: title: SQL prettify and format @@ -142,7 +142,7 @@ tools: git-memo: title: Git cheatsheet - description: Git is a decentralized version management software. With this cheatsheet you will have a quick access to the most common git commands. + description: Git is a decentralized version management software. With this cheatsheet, you will have quick access to the most common git commands. slugify-string: title: Slugify string @@ -150,7 +150,7 @@ tools: encryption: title: Encrypt / decrypt text - description: Encrypt and decrypt text clear text using crypto algorithm like AES, TripleDES, Rabbit or RC4. + description: Encrypt clear text and decrypt ciphertext using crypto algorithms like AES, TripleDES, Rabbit or RC4. random-port-generator: title: Random port generator @@ -158,11 +158,11 @@ tools: yaml-prettify: title: YAML prettify and format - description: Prettify your YAML string to a human friendly readable format. + description: Prettify your YAML string into a friendly, human-readable format. eta-calculator: title: ETA calculator - description: An ETA (Estimated Time of Arrival) calculator to know the approximate end time of a task, for example the moment of ending of a download. + description: An ETA (Estimated Time of Arrival) calculator to determine the approximate end time of a task, for example, the end time and duration of a file download. roman-numeral-converter: title: Roman numeral converter @@ -174,11 +174,11 @@ tools: bip39-generator: title: BIP39 passphrase generator - description: Generate BIP39 passphrase from existing or random mnemonic, or get the mnemonic from the passphrase. + description: Generate a BIP39 passphrase from an existing or random mnemonic, or get the mnemonic from the passphrase. base64-file-converter: title: Base64 file converter - description: Convert string, files or images into a it\'s base64 representation. + description: Convert a string, file, or image into its base64 representation. list-converter: title: List converter @@ -186,7 +186,7 @@ tools: base64-string-converter: title: Base64 string encoder/decoder - description: Simply encode and decode string into a their base64 representation. + description: Simply encode and decode strings into their base64 representation. toml-to-yaml: title: TOML to YAML @@ -198,15 +198,15 @@ tools: json-to-yaml-converter: title: JSON to YAML converter - description: Simply convert JSON to YAML with this live online converter. + description: Simply convert JSON to YAML with this online live converter. url-parser: - title: Url parser - description: Parse an url string to get all the different parts (protocol, origin, params, port, username-password, ...) + title: URL parser + description: Parse a URL into its separate constituent parts (protocol, origin, params, port, username-password, ...) iban-validator-and-parser: title: IBAN validator and parser - description: Validate and parse IBAN numbers. Check if IBAN is valid and get the country, BBAN, if it is a QR-IBAN and the IBAN friendly format. + description: Validate and parse IBAN numbers. Check if an IBAN is valid and get the country, BBAN, if it is a QR-IBAN and the IBAN friendly format. user-agent-parser: title: User-agent parser @@ -218,27 +218,27 @@ tools: case-converter: title: Case converter - description: Change the case of a string and chose between different formats + description: Transform the case of a string and choose between different formats html-entities: - title: Escape html entities - description: Escape or unescape html entities (replace <,>, &, " and \' to their html version) + title: Escape HTML entities + description: Escape or unescape HTML entities (replace characters like <,>, &, " and \' with their HTML version) json-prettify: title: JSON prettify and format - description: Prettify your JSON string to a human friendly readable format. + description: Prettify your JSON string into a friendly, human-readable format. docker-run-to-docker-compose-converter: title: Docker run to Docker compose converter - description: Turns docker run commands into docker-compose files! + description: Transforms "docker run" commands into docker-compose files! mac-address-lookup: title: MAC address lookup description: Find the vendor and manufacturer of a device by its MAC address. mime-types: - title: Mime types - description: Convert mime types to extensions and vice-versa. + title: MIME types + description: Convert MIME types to file extensions and vice-versa. toml-to-json: title: TOML to JSON @@ -250,19 +250,19 @@ tools: qrcode-generator: title: QR Code generator - description: Generate and download QR-code for an url or just a text and customize the background and foreground colors. + description: Generate and download a QR code for a URL (or just plain text), and customize the background and foreground colors. wifi-qrcode-generator: title: WiFi QR Code generator - description: Generate and download QR-codes for quick connections to WiFi networks. + description: Generate and download QR codes for quick connections to WiFi networks. xml-formatter: title: XML formatter - description: Prettify your XML string to a human friendly readable format. + description: Prettify your XML string into a friendly, human-readable format. temperature-converter: title: Temperature converter - description: Temperature degrees conversions for Kelvin, Celsius, Fahrenheit, Rankine, Delisle, Newton, Réaumur and Rømer. + description: Degrees temperature conversions for Kelvin, Celsius, Fahrenheit, Rankine, Delisle, Newton, Réaumur, and Rømer. chmod-calculator: title: Chmod calculator @@ -270,11 +270,11 @@ tools: rsa-key-pair-generator: title: RSA key pair generator - description: Generate new random RSA private and public key pem certificates. + description: Generate a new random RSA private and public pem certificate key pair. html-wysiwyg-editor: title: HTML WYSIWYG editor - description: Online HTML editor with feature-rich WYSIWYG editor, get the source code of the content immediately. + description: Online, feature-rich WYSIWYG HTML editor which generates the source code of the content immediately. yaml-to-toml: title: YAML to TOML @@ -302,15 +302,15 @@ tools: ipv4-subnet-calculator: title: IPv4 subnet calculator - description: Parse your IPv4 CIDR blocks and get all the info you need about your sub network. + description: Parse your IPv4 CIDR blocks and get all the info you need about your subnet. og-meta-generator: title: Open graph meta generator - description: Generate open-graph and socials html meta tags for your website. + description: Generate open-graph and socials HTML meta tags for your website. ipv6-ula-generator: title: IPv6 ULA generator - description: Generate your own local, non-routable IP addresses on your network according to RFC4193. + description: Generate your own local, non-routable IP addresses for your network according to RFC4193. hash-text: title: Hash text @@ -330,7 +330,7 @@ tools: json-minify: title: JSON minify - description: Minify and compress your JSON by removing unnecessary white spaces. + description: Minify and compress your JSON by removing unnecessary whitespace. ulid-generator: title: ULID generator @@ -342,31 +342,31 @@ tools: base-converter: title: Integer base converter - description: Convert number between different bases (decimal, hexadecimal, binary, octal, base64, ...) + description: Convert a number between different bases (decimal, hexadecimal, binary, octal, base64, ...) yaml-to-json-converter: title: YAML to JSON converter - description: Simply convert YAML to JSON with this live online converter. + description: Simply convert YAML to JSON with this online live converter. uuid-generator: title: UUIDs generator description: A Universally Unique Identifier (UUID) is a 128-bit number used to identify information in computer systems. The number of possible UUIDs is 16^32, which is 2^128 or about 3.4x10^38 (which is a lot!). ipv4-address-converter: - title: Ipv4 address converter - description: Convert an ip address into decimal, binary, hexadecimal or event in ipv6 + title: IPv4 address converter + description: Convert an IP address into decimal, binary, hexadecimal, or even an IPv6 representation of it. text-statistics: title: Text statistics - description: Get information about a text, the amount of characters, the amount of words, it\'s size, ... + description: Get information about a text, the number of characters, the number of words, its size in bytes, ... text-to-nato-alphabet: title: Text to NATO alphabet - description: Transform text into NATO phonetic alphabet for oral transmission. + description: Transform text into the NATO phonetic alphabet for oral transmission. basic-auth-generator: title: Basic auth generator - description: Generate a base64 basic auth header from an username and a password. + description: Generate a base64 basic auth header from a username and password. text-to-unicode: title: Text to Unicode @@ -374,7 +374,7 @@ tools: ipv4-range-expander: title: IPv4 range expander - description: Given a start and an end IPv4 address this tool calculates a valid IPv4 network with its CIDR notation. + description: Given a start and an end IPv4 address, this tool calculates a valid IPv4 subnet along with its CIDR notation. text-diff: title: Text diff @@ -385,9 +385,9 @@ tools: description: Generate and validate time-based OTP (one time password) for multi-factor authentication. url-encoder: - title: Encode/decode url formatted strings - description: Encode to url-encoded format (also known as "percent-encoded") or decode from it. + title: Encode/decode URL-formatted strings + description: Encode text to URL-encoded format (also known as "percent-encoded"), or decode from it. text-to-binary: title: Text to ASCII binary - description: Convert text to its ASCII binary representation and vice versa. + description: Convert text to its ASCII binary representation and vice-versa. From 2c2fb216e36e5c4be0a6f6e2977da7a7363535f7 Mon Sep 17 00:00:00 2001 From: steffenrapp <88974099+steffenrapp@users.noreply.github.com> Date: Fri, 10 May 2024 10:57:42 +0200 Subject: [PATCH 04/41] feat(i18n): added German translation (#1038) * feat(i18n): German translation * more * more * more * rest --- locales/de.yml | 455 ++++++++++++++++++ .../i18n/components/locale-selector.vue | 1 + 2 files changed, 456 insertions(+) create mode 100644 locales/de.yml diff --git a/locales/de.yml b/locales/de.yml new file mode 100644 index 00000000..5a47c85d --- /dev/null +++ b/locales/de.yml @@ -0,0 +1,455 @@ +'404': + notFound: 404 Nicht gefunden + sorry: Entschuldigung, diese Seite scheint nicht zu existieren + maybe: >- + Vielleicht macht der Cache etwas Seltsames. Mit einem erzwungenen Neuladen + versuchen? + backHome: Zurück zur Startseite +home: + categories: + newestTools: Neueste Tools + favoriteTools: Deine Lieblingstools + allTools: Alle Tools + subtitle: Praktische Tools für Entwickler + toggleMenu: Menü umschalten + home: Startseite + uiLib: UI-Bibliothek + support: Unterstütze die Entwicklung von IT-Tools + buyMeACoffee: Kauf mir einen Kaffee + follow: + title: Magst du IT-Tools? + p1: Gib uns einen Stern auf + githubRepository: IT-Tools GitHub-Repository + p2: oder folge uns auf + twitterAccount: IT-Tools Twitter-Konto + thankYou: Vielen Dank! + nav: + github: GitHub-Repository + githubRepository: IT-Tools GitHub-Repository + twitter: Twitter-Konto + twitterAccount: IT-Tools Twitter-Konto + about: Über IT-Tools + aboutLabel: Über + darkMode: Dunkelmodus + lightMode: Hellmodus + mode: Wechseln zwischen dunklem/hellem Modus +about: + content: > + # Über IT-Tools + + Diese wunderbare Website, erstellt mit ❤ von [Corentin + Thomasset](https://github.com/CorentinTh), sammelt nützliche Tools für + Entwickler und Menschen, die in der IT arbeiten. Wenn du sie nützlich + findest, teile sie gerne mit Personen, von denen du denkst, dass sie sie + ebenfalls nützlich finden könnten, und vergiss nicht, sie in deiner + Lesezeichenleiste zu speichern! + + IT-Tools ist Open Source (unter der MIT-Lizenz) und kostenlos und wird es + 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 + ermutigen möchtest, mehr Tools hinzuzufügen, überlege bitte, mich durch + [Sponsoring](https://www.buymeacoffee.com/cthmsst) zu unterstützen. + + ## Technologien + + IT-Tools wurde mit Vue.js (Vue 3) und der Naive UI-Komponentenbibliothek + erstellt und wird von Vercel gehostet und kontinuierlich bereitgestellt. In + einigen Tools werden Drittanbieter-Open-Source-Bibliotheken verwendet. Du + findest die vollständige Liste in der + [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json)-Datei + des Repositorys. + + ## Einen Fehler gefunden? Ein Tool fehlt? + + Wenn du ein Tool benötigst, das hier noch nicht vorhanden ist, und du + denkst, dass es nützlich sein könnte, bist du herzlich eingeladen, einen + Feature-Request im + [Issues-Bereich](https://github.com/CorentinTh/it-tools/issues/new/choose) + im GitHub-Repository einzureichen. + + Und wenn du einen Fehler gefunden hast oder etwas nicht wie erwartet + funktioniert, melde bitte einen Fehler im + [Issues-Bereich](https://github.com/CorentinTh/it-tools/issues/new/choose) + im GitHub-Repository. +favoriteButton: + remove: Aus Favoriten entfernen + add: Zu Favoriten hinzufügen +toolCard: + new: Neu +search: + label: Suche +tools: + categories: + favorite-tools: Deine Lieblingstools + crypto: Krypto + converter: Konverter + web: Web + images and videos: Bilder & Videos + development: Entwicklung + network: Netzwerk + math: Mathematik + measurement: Messung + text: Text + data: Daten + password-strength-analyser: + title: Passwortstärken-Analysator + description: >- + Ermittle die Stärke deines Passworts mit diesem Client-seitigen + Passwortstärken-Analysator und Tool zur Schätzung der Knackzeit. + chronometer: + title: Chronometer + description: >- + Überwache die Dauer einer Sache. Im Grunde ein Chronometer mit einfachen + Chronometerfunktionen. + token-generator: + title: Token-Generator + description: >- + Generiere eine zufällige Zeichenfolge mit den von dir gewünschten Zeichen, + Groß- oder Kleinbuchstaben, Zahlen und/oder Symbolen. + uppercase: Großbuchstaben (ABC...) + lowercase: Kleinbuchstaben (abc...) + numbers: Zahlen (123...) + symbols: Symbole (!-;...) + length: Länge + tokenPlaceholder: Der Token ... + copied: Token in die Zwischenablage kopiert + button: + copy: Kopieren + refresh: Aktualisieren + percentage-calculator: + title: Prozentrechner + description: >- + Berechne einfach Prozentsätze von einem Wert zu einem anderen Wert oder + von einem Prozentsatz zu einem Wert. + svg-placeholder-generator: + title: SVG-Platzhalter-Generator + description: >- + Generiere SVG-Bilder, die als Platzhalter in deinen Anwendungen verwendet + werden können. + json-to-csv: + title: JSON zu CSV + description: Konvertiere JSON mit automatischer Headererkennung in CSV. + camera-recorder: + title: Kamera-Rekorder + description: Mache ein Foto oder nimm ein Video von deiner Webcam oder Kamera auf. + keycode-info: + title: Keycode-Info + description: >- + Finde den JavaScript-Keycode, den Code, den Standort und die Modifikatoren + einer beliebigen gedrückten Taste. + emoji-picker: + title: Emoji-Picker + description: >- + Einfaches Kopieren und Einfügen von Emojis. Erhalte außerdem den Unicode- + und Codepunkt-Wert jedes Emojis. + color-converter: + title: Farbkonverter + description: >- + Konvertiere Farben zwischen den verschiedenen Formaten (Hex, RGB, HSL und + CSS-Name). + bcrypt: + title: Bcrypt + description: >- + Hashen und Vergleichen von Strings mit bcrypt. Bcrypt ist eine auf der + Blowfish-Chiffre basierende Hash-Funktion. + crontab-generator: + title: Crontab-Generator + description: >- + Überprüfe und generiere Crontab und erhalte die menschenlesbare + Beschreibung des Cron-Zeitplans. + http-status-codes: + title: HTTP-Statuscodes + description: Liste aller HTTP-Statuscodes, ihrer Namen und ihrer Bedeutung. + sql-prettify: + title: SQL verschönern und formatieren + description: >- + Formatiere und verschönere deine SQL-Abfragen online (unterstützt + verschiedene SQL-Dialekte). + benchmark-builder: + title: Benchmark-Builder + description: >- + Vergleiche ganz einfach die Ausführungszeit von Aufgaben mit diesem sehr + einfachen Online-Benchmark-Builder. + git-memo: + title: Git-Spickzettel + description: >- + Git ist eine dezentrale Versionsverwaltungssoftware. Mit diesem + Spickzettel hast du schnellen Zugriff auf die gängigsten Git-Befehle. + slugify-string: + title: Slugify String + description: Mache einen String URL-, Dateinamen- und ID-sicher. + encryption: + title: Text verschlüsseln / entschlüsseln + description: >- + Verschlüssele und entschlüssele Klartext mithilfe von Kryptoalgorithmen + wie AES, TripleDES, Rabbit oder RC4. + random-port-generator: + title: Zufälliger Port-Generator + description: >- + Generiere zufällige Portnummern außerhalb des Bereichs der "bekannten" + Ports (0-1023). + yaml-prettify: + title: YAML verschönern und formatieren + description: Verschönere deinen YAML-String in ein menschenlesbares Format. + eta-calculator: + title: ETA-Rechner + description: >- + Ein ETA (Estimated Time of Arrival)-Rechner, um die ungefähre Endzeit + einer Aufgabe zu erfahren, z. B. den Zeitpunkt des Endes eines Downloads. + roman-numeral-converter: + title: Römische Zahlen Konverter + description: >- + Konvertiere römische Zahlen in Dezimalzahlen und Dezimalzahlen in römische + Zahlen. + hmac-generator: + title: HMAC-Generator + description: >- + Berechnet einen hashbasierten Nachrichtenauthentifizierungscode (HMAC) + unter Verwendung eines geheimen Schlüssels und deiner bevorzugten + Hash-Funktion. + bip39-generator: + title: BIP39-Passphrasengenerator + description: >- + Generiere BIP39-Passphrasen aus vorhandener oder zufälliger Mnemonik oder + erhalte die Mnemonik aus der Passphrase. + base64-file-converter: + title: Base64-Dateikonverter + description: Konvertiere Strings, Dateien oder Bilder in ihre Base64-Repräsentation. + list-converter: + title: Listenkonverter + description: >- + Dieses Tool kann spaltenbasierte Daten verarbeiten und verschiedene + Änderungen (transponieren, Präfix und Suffix hinzufügen, Liste umkehren, + Liste sortieren, Werte in Kleinbuchstaben umwandeln, Werte abschneiden) + auf jede Zeile anwenden. + base64-string-converter: + title: Base64-String-Encoder/Decoder + description: Codiere und decodiere Strings einfach in ihre Base64-Repräsentation. + toml-to-yaml: + title: TOML zu YAML + description: Parse und konvertiere TOML zu YAML. + math-evaluator: + title: Mathematischer Auswerter + description: >- + Ein Taschenrechner zum Auswerten mathematischer Ausdrücke. Du kannst + Funktionen wie sqrt, cos, sin, abs usw. verwenden. + json-to-yaml-converter: + title: JSON zu YAML + description: Konvertiere JSON einfach in YAML mit diesem Live-Online-Konverter. + url-parser: + title: URL-Parser + description: >- + Parse eine URL-Zeichenfolge, um alle verschiedenen Teile (Protokoll, + Ursprung, Parameter, Port, Benutzername-Passwort usw.) zu erhalten. + iban-validator-and-parser: + title: IBAN-Validator und -Parser + description: >- + Validiere und parse IBAN-Nummern. Überprüfe, ob die IBAN gültig ist, und + erhalte das Land, BBAN, ob es sich um eine QR-IBAN handelt und das + IBAN-freundliche Format. + user-agent-parser: + title: User-Agent-Parser + description: >- + Erkenne und parse Browser, Engine, Betriebssystem, CPU und + Gerätetyp/-modell aus einer User-Agent-Zeichenfolge. + numeronym-generator: + title: Numeronym-Generator + description: >- + Ein Numeronym ist ein Wort, bei dem eine Zahl verwendet wird, um eine + Abkürzung zu bilden. Zum Beispiel ist "i18n" ein Numeronym für + "internationalization", wobei 18 für die Anzahl der Buchstaben zwischen + dem ersten "i" und dem letzten "n" im Wort steht. + case-converter: + title: Fall-Konverter + description: >- + Ändere den Fall eines Strings und wähle zwischen verschiedenen Formaten + aus. + html-entities: + title: HTML-Entity-Escape + description: >- + Escape oder unescape HTML-Entitäten (ersetze <, >, &, " und ' durch ihre + HTML-Version). + json-prettify: + title: JSON verschönern und formatieren + description: Verschönere deinen JSON-String in ein menschenlesbares Format. + docker-run-to-docker-compose-converter: + title: Docker run zu Docker compose Konverter + description: Wandle docker run-Befehle in docker-compose-Dateien um! + mac-address-lookup: + title: MAC-Adressensuche + description: Finde den Anbieter und Hersteller eines Geräts anhand seiner MAC-Adresse. + mime-types: + title: MIME-Typen + description: Konvertiere MIME-Typen in Erweiterungen und umgekehrt. + toml-to-json: + title: TOML zu JSON + description: Parse und konvertiere TOML zu JSON. + lorem-ipsum-generator: + title: Lorem Ipsum Generator + description: >- + Lorem Ipsum ist ein Platzhaltertext, der häufig verwendet wird, um die + visuelle Form eines Dokuments oder einer Schriftart ohne Verwendung von + bedeutendem Inhalt zu demonstrieren. + qrcode-generator: + title: QR-Code-Generator + description: >- + Generiere und downloade QR-Codes für eine URL oder einfach einen Text und + passe die Hintergrund- und Vordergrundfarben an. + wifi-qrcode-generator: + title: WLAN-QR-Code-Generator + description: >- + Generiere und lade QR-Codes für schnelle Verbindungen zu WLAN-Netzwerken + herunter. + xml-formatter: + title: XML-Formatter + description: Verschönere deinen XML-String in ein menschenlesbares Format. + temperature-converter: + title: Temperaturkonverter + description: >- + Temperaturgradumrechnungen für Kelvin, Celsius, Fahrenheit, Rankine, + Delisle, Newton, Réaumur und Rømer. + chmod-calculator: + title: Chmod-Rechner + description: >- + Berechne deine Chmod-Berechtigungen und -Befehle mit diesem + Online-Chmod-Rechner. + rsa-key-pair-generator: + title: RSA-Schlüsselpaar-Generator + description: Generiere neue zufällige RSA-Private- und Public-Key-PEM-Zertifikate. + html-wysiwyg-editor: + title: HTML-WYSIWYG-Editor + description: >- + Online-HTML-Editor mit funktionsreichem WYSIWYG-Editor, erhalte sofort den + Quellcode des Inhalts. + yaml-to-toml: + title: YAML zu TOML + description: Parse und konvertiere YAML zu TOML. + mac-address-generator: + title: MAC-Adressen-Generator + description: >- + Gebe die Menge und das Präfix ein. MAC-Adressen werden in deiner gewählten + Schreibweise (Groß- oder Kleinbuchstaben) generiert. + json-diff: + title: JSON-Unterschied + description: Vergleiche zwei JSON-Objekte und erhalte die Unterschiede zwischen ihnen. + jwt-parser: + title: JWT-Parser + description: >- + Parse und decodiere deinen JSON-Web-Token (JWT) und zeige dessen Inhalt + an. + date-converter: + title: Datum-Uhrzeit-Konverter + description: Konvertiere Datum und Uhrzeit in verschiedene Formate. + phone-parser-and-formatter: + title: Telefonnummer-Parser und -Formatter + description: >- + Parse, validiere und formatiere Telefonnummern. Erhalte Informationen zur + Telefonnummer, wie z. B. die Landesvorwahl, den Typ usw. + ipv4-subnet-calculator: + title: IPv4-Subnetzrechner + description: >- + Parse deine IPv4-CIDR-Blöcke und erhalte alle Informationen, die du über + dein Subnetz benötigst. + og-meta-generator: + title: Open Graph Meta-Generator + description: Generiere Open Graph- und Social-HTML-Metatags für deine Website. + ipv6-ula-generator: + title: IPv6-ULA-Generator + description: >- + Generiere deine eigenen lokalen, nicht routbaren IP-Adressen in deinem + Netzwerk gemäß RFC4193. + hash-text: + title: Text hashen + description: >- + Hashe einen Text-String mit der von dir benötigten Funktion: MD5, SHA1, + SHA256, SHA224, SHA512, SHA384, SHA3 oder RIPEMD160 + json-to-toml: + title: JSON zu TOML + description: Parse und konvertiere JSON zu TOML. + device-information: + title: Geräteinformationen + description: >- + Informationen zu deinem aktuellen Gerät (Bildschirmgröße, Pixelverhältnis, + Benutzeragent, ...) erhalten. + pdf-signature-checker: + title: PDF-Signaturprüfer + description: >- + Überprüfe die Signaturen einer PDF-Datei. Eine signierte PDF-Datei enthält + eine oder mehrere Signaturen, die verwendet werden können, um + festzustellen, ob der Inhalt der Datei seit dem Zeitpunkt der Signierung + geändert wurde. + json-minify: + title: JSON minifizieren + description: >- + Minifiziere und komprimiere dein JSON, indem unnötige Leerzeichen entfernt + werden. + ulid-generator: + title: ULID-Generator + description: >- + Generiere zufällige Universally Unique Lexicographically Sortable + Identifier (ULID). + string-obfuscator: + title: String-Verschleierer + description: >- + Verschleiere einen String (wie ein Secret, eine IBAN oder ein Token), um + ihn weitergeben zu können und identifizierbar zu machen, ohne seinen + Inhalt preiszugeben. + base-converter: + title: Ganzzahl-Basiskonverter + description: >- + Konvertiere Zahlen zwischen verschiedenen Basen (Dezimal, Hexadezimal, + Binär, Oktal, Base64, ...). + yaml-to-json-converter: + title: YAML zu JSON + description: Konvertiere YAML einfach in JSON mit diesem Live-Online-Konverter. + uuid-generator: + title: UUID-Generator + description: >- + Ein Universally Unique Identifier (UUID) ist eine 128-Bit-Nummer, die zur + Identifizierung von Informationen in Computersystemen verwendet wird. Die + Anzahl der möglichen UUIDs beträgt 16^32, was 2^128 oder etwa 3,4x10^38 + entspricht (was ziemlich viel ist!). + ipv4-address-converter: + title: IPv4-Adresskonverter + description: >- + Konvertiere eine IP-Adresse in Dezimal, Binär, Hexadezimal oder sogar in + IPv6. + text-statistics: + title: Textstatistiken + description: >- + Informationen zu einem Text erhalten, wie die Anzahl der Zeichen, die + Anzahl der Wörter, die Größe usw. + text-to-nato-alphabet: + title: Text zu NATO-Alphabet + description: >- + Wandle Text in das NATO-Phonetik-Alphabet für die mündliche Übermittlung + um. + basic-auth-generator: + title: Basic-Auth-Generator + description: >- + Generiere einen Base64-Basic-Auth-Header aus einem Benutzernamen und einem + Passwort. + text-to-unicode: + title: Text zu Unicode + description: Parse und konvertiere Text in Unicode und umgekehrt. + ipv4-range-expander: + title: IPv4-Bereichserweiterer + description: >- + Bei Angabe einer Start- und End-IPv4-Adresse berechnet dieses Tool ein + gültiges IPv4-Netzwerk mit seiner CIDR-Notation. + text-diff: + title: Textunterschied + description: Vergleiche zwei Texte und sieh die Unterschiede zwischen ihnen. + otp-generator: + title: OTP-Code-Generator + description: >- + Generiere und validiere zeitbasierte OTPs (Einmalpasswörter) für + Multi-Faktor-Authentifizierung. + url-encoder: + title: Kodieren/Decodieren von URL-formatierten Zeichenfolgen + description: >- + Kodiere zum URL-kodierten Format (auch als "prozentkodiert" bekannt) oder + decodiere es. + text-to-binary: + title: Text zu ASCII-Binär + description: Konvertiere Text in seine ASCII-Binärrepräsentation und umgekehrt. diff --git a/src/modules/i18n/components/locale-selector.vue b/src/modules/i18n/components/locale-selector.vue index 3f0c461c..45732bf9 100644 --- a/src/modules/i18n/components/locale-selector.vue +++ b/src/modules/i18n/components/locale-selector.vue @@ -3,6 +3,7 @@ const { availableLocales, locale } = useI18n(); const localesLong: Record = { en: 'English', + de: 'Deutsch', es: 'Español', fr: 'Français', pt: 'Português', From a7992340f7059bda743d3b0458038b229cb63a35 Mon Sep 17 00:00:00 2001 From: Corentin THOMASSET Date: Fri, 10 May 2024 11:28:45 +0200 Subject: [PATCH 05/41] chore(issues): improved bug issue template (#1046) --- .github/ISSUE_TEMPLATE/bug-report.md | 34 ------------------- .github/ISSUE_TEMPLATE/bug-report.yml | 48 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 34 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index c4bf532b..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve our tools -title: '[BUG] ' -labels: bug -assignees: CorentinTh ---- - -**Which tool is impacted?** -Example: the token generator - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Configuration (please complete the following information):** - -- Device: [e.g. iPhone6, ] -- OS: [e.g. iOS] -- Browser [e.g. chrome, safari] -- Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..fb9964d1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,48 @@ +name: Bug Report +description: File a bug report. +labels: ['bug', 'triage'] +assignees: + - CorentinTh +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + - type: textarea + id: bug-description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! + placeholder: Bug description + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? If you have a screenshot, you can paste it here. + placeholder: Tell us what you see! + value: 'A bug happened!' + validations: + required: true + + - type: textarea + id: version + attributes: + label: System information + description: What is you environment? You can use the `npx envinfo --system --browsers` command to get this information. + validations: + required: true + + - type: dropdown + id: app-type + attributes: + label: Where did you encounter the bug? + options: + - Public app (it-tools.tech) + - Self hosting + - Other (installations, docker, etc.) + validations: + required: true From 124284278f0b2e66a96cd8c12b416a3f574a6c2b Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Fri, 10 May 2024 11:36:49 +0200 Subject: [PATCH 06/41] refactor(auto-imports): regen auto imports --- components.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/components.d.ts b/components.d.ts index e31119b3..f2c3146f 100644 --- a/components.d.ts +++ b/components.d.ts @@ -159,6 +159,7 @@ declare module '@vue/runtime-core' { RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] + SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] From 2852c30e1f9e5b0f1c4ce2c6f4bf039dda68f96a Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Fri, 10 May 2024 11:37:14 +0200 Subject: [PATCH 07/41] chore(issues): improved issues template --- .github/ISSUE_TEMPLATE/bug-report.yml | 4 +- .github/ISSUE_TEMPLATE/feature-request.yml | 56 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index fb9964d1..d338fa3f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,4 +1,4 @@ -name: Bug Report +name: 🐞 Bug Report description: File a bug report. labels: ['bug', 'triage'] assignees: @@ -42,7 +42,7 @@ body: label: Where did you encounter the bug? options: - Public app (it-tools.tech) - - Self hosting + - A self hosted - Other (installations, docker, etc.) validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..ed666db9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,56 @@ +name: 🚀 New feature proposal +description: Propose a new feature to be added to IT-Tools. +labels: ['enhancement', 'triage'] + +body: + - type: markdown + attributes: + value: | + Thanks for your interest in the project and taking the time to fill out this feature report! + + - type: dropdown + id: request-type + attributes: + label: What type of request is this? + options: + - New tool idea + - New feature for an existing tool + - Deployment or CI/CD improvement + - Self-hosting improvement + - Other + validations: + required: true + + - type: textarea + id: feature-description + attributes: + label: Clear and concise description of the feature you are proposing + description: A clear and concise description of what the feature is. + placeholder: 'Example: a token generator tool' + validations: + required: true + + - type: textarea + id: alternative + attributes: + label: Is their example of this tool in the wild? + description: Provide link to already existing tool (like websites, apps, cli, ...) or npm packages that could be used or provide inspiration for the feature. + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Any other context or screenshots about the feature request here. + + - type: checkboxes + id: checkboxes + attributes: + label: Validations + description: Before submitting the issue, please make sure you do the following + options: + - label: Check the feature is not already implemented in the project. + required: true + - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. + required: true + - label: Check that the feature can be implemented in a client side only app (IT-Tools is client side only, no server). + required: true From 33e5294a948dbea05cdc3e123cc275f5e5b93b69 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Fri, 10 May 2024 12:07:50 +0200 Subject: [PATCH 08/41] refactor(lint): removed extra semi --- src/tools/token-generator/token-generator.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/token-generator/token-generator.service.ts b/src/tools/token-generator/token-generator.service.ts index 3733a884..f928a415 100644 --- a/src/tools/token-generator/token-generator.service.ts +++ b/src/tools/token-generator/token-generator.service.ts @@ -20,7 +20,7 @@ export function createToken({ withLowercase ? 'abcdefghijklmopqrstuvwxyz' : '', withNumbers ? '0123456789' : '', withSymbols ? '.,;:!?./-"\'#{([-|\\@)]=}*+' : '', - ].join(''); ; + ].join(''); return shuffleString(allAlphabet.repeat(length)).substring(0, length); } From 9dfd347edfc36eb181baea7768cc85d65974cc86 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Fri, 10 May 2024 12:08:15 +0200 Subject: [PATCH 09/41] docs(changelog): update changelog for 2024.05.10-33e5294 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c12c11c..7d908d43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,40 @@ 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.05.10-33e5294 + +### Features +- **i18n**: added German translation (#1038) (2c2fb21) +- **new tool**: Outlook Safelink Decoder (#911) (d3b32cc) +- **new tool**: ascii art generator (#886) (fe349ad) +- **i18n**: get locales on build (#880) (dc04615) +- **i18n**: added vi tools translations (#876) (079aa21) +- **i18n**: added zh tools translations (#874) (9c6b122) +- **i18n**: added missing locale files in tools (#863) (7f5fa00) +- **i18n**: added vietnamese language (#859) (1334bff) +- **i18n**: added spanish language (#854) (85b50bb) +- **i18n**: added portuguese language (#813) (c65ffb6) +- **i18n**: added ukrainian language (#827) (693f362) +- **new-tool**: yaml formater (#779) (fc06f01) +- **new-tool**: added unicode conversion utilities (#858) (c46207f) + +### Bug fixes +- **language**: English language cleanup (#1036) (221ddfa) +- **url-encoder, validation**: typo in validation of url-encoder.vue #1024 (cb5b462) +- **integer base converter**: support bigint (#872) (9eac9cb) +- **bcrypt tool**: allow salt rounds up to 100 (#987) (23f82d9) + +### Refactoring +- **lint**: removed extra semi (33e5294) +- **auto-imports**: regen auto imports (1242842) +- **home**: lightened tool cards (#882) (a07806c) +- **home**: removed n-grid to prevent layout shift (#881) (10e56b3) +- **i18n**: added locales per tool (#861) (95698cb) + +### Chores +- **issues**: improved issues template (2852c30) +- **issues**: improved bug issue template (#1046) (a799234) + ## Version 2023.12.21-5ed3693 ### Features From 38d568798c6de5e8968349f1e1f1ec079d3bb3b6 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Fri, 10 May 2024 12:08:16 +0200 Subject: [PATCH 10/41] chore(version): release 2024.05.10-33e5294 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd6c02e6..b8b1eeee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "it-tools", - "version": "2023.12.21-5ed3693", + "version": "2024.5.10-33e5294", "description": "Collection of handy online tools for developers, with great UX. ", "keywords": [ "productivity", From b59942ad9ff793b16d8d497d7f35f34a00e95469 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Fri, 10 May 2024 13:26:36 +0200 Subject: [PATCH 11/41] chore(node): upgraded node version in CI workflows --- .github/workflows/codeql-analysis.yml | 69 -------------------- .github/workflows/docker-nightly-release.yml | 2 +- .github/workflows/releases.yml | 2 +- 3 files changed, 2 insertions(+), 71 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 6d11e825..00000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,69 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ dev ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ dev ] - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - - steps: - - name: Checkout repository - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/docker-nightly-release.yml b/.github/workflows/docker-nightly-release.yml index 81a0898c..41dbb155 100644 --- a/.github/workflows/docker-nightly-release.yml +++ b/.github/workflows/docker-nightly-release.yml @@ -32,7 +32,7 @@ jobs: - run: corepack enable - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 cache: 'pnpm' - name: Install dependencies diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 8ed4099d..d0d3febd 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -61,7 +61,7 @@ jobs: - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 cache: 'pnpm' - name: Install dependencies From 5a7b0f9636de45f71296165a47cb12931ea02097 Mon Sep 17 00:00:00 2001 From: Corentin THOMASSET Date: Mon, 13 May 2024 10:11:47 +0200 Subject: [PATCH 12/41] chore(issues): removed old issue templates (#1077) --- .github/ISSUE_TEMPLATE/feature-request.yml | 2 +- .github/ISSUE_TEMPLATE/new-tool-request.md | 19 ------------------- .github/ISSUE_TEMPLATE/other-request.md | 13 ------------- .github/ISSUE_TEMPLATE/tool-improvement.md | 13 ------------- 4 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/new-tool-request.md delete mode 100644 .github/ISSUE_TEMPLATE/other-request.md delete mode 100644 .github/ISSUE_TEMPLATE/tool-improvement.md diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index ed666db9..edae822e 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -1,5 +1,5 @@ name: 🚀 New feature proposal -description: Propose a new feature to be added to IT-Tools. +description: Propose a new feature/enhancement or tool idea for IT-Tools labels: ['enhancement', 'triage'] body: diff --git a/.github/ISSUE_TEMPLATE/new-tool-request.md b/.github/ISSUE_TEMPLATE/new-tool-request.md deleted file mode 100644 index a67a9cd0..00000000 --- a/.github/ISSUE_TEMPLATE/new-tool-request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: New tool request -about: Suggest a new tool idea -title: '[NEW TOOL]' -labels: new tool -assignees: CorentinTh ---- - -**What tool do you want?** -Example: a token generator - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Is their example of this tool in the wild?** -Provide link to already existing tool or npm packages if any exists - -**Additional context** -Add any other context about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/other-request.md b/.github/ISSUE_TEMPLATE/other-request.md deleted file mode 100644 index b4901ed4..00000000 --- a/.github/ISSUE_TEMPLATE/other-request.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Other request -about: Any request that does not concern a tool creation, a new feature request on a tool or a bug -title: '[OTHER] ' -labels: -assignees: CorentinTh ---- - -**Describe the solution you'd like** -A clear and concise description of what you want. - -**Additional context** -Add any other context about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/tool-improvement.md b/.github/ISSUE_TEMPLATE/tool-improvement.md deleted file mode 100644 index 8ff8bb6c..00000000 --- a/.github/ISSUE_TEMPLATE/tool-improvement.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Tool improvement -about: Improvement on an existing tool -title: '[TOOL IMPROVEMENT]' -labels: enhancement -assignees: CorentinTh ---- - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Additional context** -Add any other context about the feature request here. From a0bc3468b256739d83b08ef25fb5709281260f69 Mon Sep 17 00:00:00 2001 From: Corentin THOMASSET Date: Mon, 13 May 2024 10:44:04 +0200 Subject: [PATCH 13/41] chore(issues): prevent empty issues (#1078) --- .github/ISSUE_TEMPLATE/config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3ba13e0c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false From 81cf6b54835499952ee39c5cb9bb20a196adc625 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Mon, 13 May 2024 10:54:18 +0200 Subject: [PATCH 14/41] docs(changelog): update changelog for 2024.05.13-a0bc346 --- CHANGELOG.md | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d908d43..984fa58e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ 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.05.10-33e5294 +## Version 2024.05.13-a0bc346 ### Features - **i18n**: added German translation (#1038) (2c2fb21) @@ -33,12 +33,20 @@ All notable changes to this project will be documented in this file. See [standa - **i18n**: added locales per tool (#861) (95698cb) ### Chores +- **issues**: prevent empty issues (#1078) (a0bc346) +- **issues**: removed old issue templates (#1077) (5a7b0f9) +- **node**: upgraded node version in CI workflows (b59942a) +- **version**: release 2024.05.10-33e5294 (38d5687) - **issues**: improved issues template (2852c30) - **issues**: improved bug issue template (#1046) (a799234) +### Documentation +- **changelog**: update changelog for 2024.05.10-33e5294 (9dfd347) + ## Version 2023.12.21-5ed3693 ### Features + - **i18n**: improve chinese i18n (#757) (2e56641) - **i18n**: add tooltip and favoriteButton i18n (#756) (a1037cf) - **i18n**: add Chinese translation base (#718) (8f99eb6) @@ -46,6 +54,7 @@ All notable changes to this project will be documented in this file. See [standa - **new tool**: numeronym generator (#729) (e07e2ae) ### Bug fixes + - **jwt-parser**: jwt claim array support (#799) (5ed3693) - **camera-recorder**: stop camera on navigation (#782) (80e46c9) - **doc**: updated create new tool command in readme (#762) (7a70dbb) @@ -54,6 +63,7 @@ All notable changes to this project will be documented in this file. See [standa - **eta**: corrected example (#737) (821cbea) ### Refactoring + - **about, i18n**: improved i18n dx with markdown (#753) (bd3edcb) - **token, i18n**: complete fr translation (#752) (de1ee69) - **uuid generator**: uuid version picker (#751) (38586ca) @@ -63,6 +73,7 @@ All notable changes to this project will be documented in this file. See [standa - **bcrypt**: fix input label align (#721) (093ff31) ### Chores + - **deps**: switched from oui to oui-data for mac address lookup (#693) (0fe9a20) - **deps**: update unocss monorepo to ^0.57.0 (#638) (2e396d8) - **docker**: added armv7 plateform for docker releases (#722) (fe1de8c) @@ -70,19 +81,23 @@ All notable changes to this project will be documented in this file. See [standa ## Version 2023.11.02-7d94e11 ### Features + - **i18n**: language selector (#710) (e86fd96) ### Bug fixes + - **dockerfile**: revert replacement of nginx image with non-privileged one (#716) (7d94e11) - **encryption**: alert on decryption error (#711) (02b0d0d) ### Refactoring + - **math-evaluator**: improved description (e87f4b1) - **math-evaluator**: improved search and UX (#713) (58de897) ## Version 2023.11.01-e164afb ### Features + - **command-palette**: clear prompt on palette close (#708) (d013696) - **command-palette**: added about page in command palette (99b1eb9) - **new tool**: random MAC address generator (#657) (cc3425d) @@ -101,11 +116,13 @@ All notable changes to this project will be documented in this file. See [standa - **new tool**: text diff and comparator (#588) (81bfe57) ### Bug fixes + - **deps**: fix issue on slugify (#593) (#673) (720201a) - **deps**: update dependency monaco-editor to ^0.43.0 (#620) (e371ef7) - **deps**: update dependency sql-formatter to v13 (#606) (c7d4562) ### Refactoring + - **ui**: better ui demo preview menu (#664) (015c673) - **color-converter**: improved color-converter UX (#701) (abb8335) - **docker**: improved docker config (#700) (020e9cb) @@ -122,6 +139,7 @@ All notable changes to this project will be documented in this file. See [standa - **bcrypt**: fix typo (#604) (e18bae1) ### Chores + - **deps**: clean unused dependencies (#709) (e164afb) - **deps**: update docker/setup-qemu-action action to v3 (#627) (4365226) - **deps**: update docker/setup-buildx-action action to v3 (#626) (57ecda1) @@ -136,19 +154,23 @@ All notable changes to this project will be documented in this file. See [standa - **deps**: update dependency typescript to ~5.2.0 (#587) (f3e14fc) ### Doc + - **readme**: added contributors list (#622) (557b304) - **hosting**: added cloudron in the other hosting solutions section (#589) (06c3547) ## Version 2023.08.21-6f93cba ### Features + - **copy**: support legacy copy to clipboard for older browser (#581) (6f93cba) - **new tool**: string obfuscator (#575) (c58d6e3) ### Bug fixes + - **deps**: update dependency sql-formatter to v12 (#520) (2bcb77a) ### Chores + - **deps**: switched to fucking typescript v5 (#501) (76b2761) - **deps**: update dependency @antfu/eslint-config to ^0.40.0 (#552) (6ff9a01) - **deps**: update dependency prettier to v3 (#564) (a2b9b15) @@ -158,6 +180,7 @@ All notable changes to this project will be documented in this file. See [standa ## Version 2023.08.16-9bd4ad4 ### Features + - **Case Converter**: Add lowercase and uppercase (#534) (7b6232a) - **new tool**: emoji picker (#551) (93f7cf0) - **ui**: added c-select in the ui lib (#550) (dfa1ba8) @@ -178,6 +201,7 @@ All notable changes to this project will be documented in this file. See [standa - **base64-string-converter**: switch to encode and decode url safe base64 strings (#392) (0b20f1c) ### Bug fixes + - **deps**: update dependency uuid to v9 (#566) (5e12991) - **deps**: update dependency mathjs to v11 (#519) (7924456) - **deps**: update dependency @vueuse/router to v10 (#516) (ea0f27c) @@ -197,6 +221,7 @@ All notable changes to this project will be documented in this file. See [standa - **ipv4-converter**: removed readonly on input (7aed9c5) ### Refactoring + - **navbar**: consistent spacing in navbar buttons (#507) (30f88fc) - **ui**: remove n-text (#506) (72c98a3) - **ui**: replaced some n-input to c-input (#505) (05ea545) @@ -209,6 +234,7 @@ All notable changes to this project will be documented in this file. See [standa - **ui**: replaced some n-input with c-input-text (f7fc779) ### Chores + - **deps**: update dependency vitest to ^0.34.0 (#562) (9bd4ad4) - **deps**: update dependency node to v18.17.1 (#560) (65a9474) - **deps**: update dependency unocss to ^0.55.0 (#561) (85cc7a8) @@ -249,47 +275,58 @@ All notable changes to this project will be documented in this file. See [standa - **lint**: switched to a better lint config (33c9b66) ### Refacor + - **transformers**: use monospace font for JSON and SQL text areas (#476) (ba4876d) ### Documentation + - **ide**: updated vscode extensions settings (#472) (847323c) ### Chors + - **deps**: updated vueuse dependency version (8515c24) ## Version 2023.05.14-77f2efc ### Features + - **list-converter**: a small converter who deals with column based data and do some stuff with it (#387) (83a7b3b) - **new tool**: phone parser and normalizer (ce3150c) ### Bug fixes + - **phone-parser**: use default country code (a43c546) - **home**: prevent weird blue border on card (3f6c8f0) ### Refactoring + - **ui**: replaced some n-input with c-input-text (77f2efc) ### Chores + - **issues**: updated new tool request issue template (edae4c6) ### Ui-lib + - **new-component**: added text input component in the c-lib (aad8d84) - **button**: size variants (401f13f) ## Version 2023.04.23-92bd835 ### Features + - **ui-lib**: demo pages for c-lib components (92bd835) - **new-tool**: diff of two json objects (362f2fa) - **ipv4-range-expander**: expands a given IPv4 start and end address to a valid IPv4 subnet (#366) (df989e2) - **date converter**: auto focus main input (6d22025) ### Bug fixes + - **ts**: cleaned legacy typechecking warning (e88c1d5) - **mac-address-lookup**: added copy handler on button click (c311e38) ### Refactoring + - **ui-lib**: prevent c-button to shrink (61ece23) - **ui**: replaced naive ui cards with custom ones (f080933) - **clean**: removed unused lodash import (bb32513) @@ -299,48 +336,60 @@ All notable changes to this project will be documented in this file. See [standa ## Version 2023.04.14-dbad773 ### Features + - **new-tool**: http status codes (8355bd2) ### Refactoring + - **uuid-generator**: prevent NaN in quantity (6fb4994) ### Chores + - **release**: create a github release on new version (dbad773) - **version**: reset CHANGELOG content to support new format (85cb0ff) ## Version 2023.04.14-f9b77b7 ### Features + - **new-tool**: http status codes (8355bd2) ### Refactoring + - **uuid-generator**: prevent NaN in quantity (6fb4994) ### Chores + - **release**: create a github release on new version (f9b77b7) - **version**: reset CHANGELOG content to support new format (85cb0ff) ## Version 2023.04.14-2f0d239 ### Features + - **new-tool**: http status codes (8355bd2) ### Refactoring + - **uuid-generator**: prevent NaN in quantity (6fb4994) ### Chores + - **release**: create a github release on new version (2f0d239) - **version**: reset CHANGELOG content to support new format (85cb0ff) ## Version 2023.04.14-474cae4 ### Features + - **new-tool**: http status codes (8355bd2) ### Refactoring + - **uuid-generator**: prevent NaN in quantity (6fb4994) ### Chores + - **release**: create a github release on new version (474cae4) - **version**: reset CHANGELOG content to support new format (85cb0ff) diff --git a/package.json b/package.json index b8b1eeee..801f5c0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "it-tools", - "version": "2024.5.10-33e5294", + "version": "2024.5.13-b62e12f", "description": "Collection of handy online tools for developers, with great UX. ", "keywords": [ "productivity", From e876d0360812eab1255e414102a44d65f7c1bfaf Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Mon, 13 May 2024 10:54:18 +0200 Subject: [PATCH 15/41] chore(version): release 2024.05.13-a0bc346 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 801f5c0f..9f39ff1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "it-tools", - "version": "2024.5.13-b62e12f", + "version": "2024.5.13-a0bc346", "description": "Collection of handy online tools for developers, with great UX. ", "keywords": [ "productivity", From 30144aa3f51f5d0dc90f64cf888bc6f83de11b10 Mon Sep 17 00:00:00 2001 From: sharevb Date: Mon, 20 May 2024 22:13:55 +0200 Subject: [PATCH 16/41] feat(base64): Base64 enhancements (#905) * fix(base64): use js-base64 to handle non ascii text Use js-base64 to handle non ascii text and ignore whitespaces Fix #879 and #409 * fix(base64): use js-base64 to handle non ascii text Use js-base64 to handle non ascii text and ignore whitespaces Fix #879 and #409 * feat(base64 file converter): add a filename and extension fields Add filename and extension (auto filled if data url) to allow downloading with right extension and filename Fix #788 * feat(base64 file converter): add a preview image Fix #594. Taken from #595 (thanks @SAF2k) --- package.json | 1 + pnpm-lock.yaml | 19 ++-- src/composable/downloadBase64.ts | 91 +++++++++++++++---- .../base64-file-converter.vue | 67 +++++++++++++- src/utils/base64.test.ts | 13 +-- src/utils/base64.ts | 11 ++- 6 files changed, 164 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 9f39ff1d..c6cb7757 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", + "js-base64": "^3.7.6", "json5": "^2.2.3", "jwt-decode": "^3.1.2", "libphonenumber-js": "^1.10.28", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd6c38c9..8619d8c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ dependencies: ibantools: specifier: ^4.3.3 version: 4.3.3 + js-base64: + specifier: ^3.7.6 + version: 3.7.7 json5: specifier: ^2.2.3 version: 2.2.3 @@ -3351,7 +3354,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.7.2(vue@3.3.4) + '@vueuse/shared': 10.8.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3993,10 +3996,10 @@ packages: - vue dev: false - /@vueuse/shared@10.7.2(vue@3.3.4): - resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==} + /@vueuse/shared@10.8.0(vue@3.3.4): + resolution: {integrity: sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==} dependencies: - vue-demi: 0.14.6(vue@3.3.4) + vue-demi: 0.14.7(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -6472,6 +6475,10 @@ packages: hasBin: true dev: true + /js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + dev: false + /js-beautify@1.14.6: resolution: {integrity: sha512-GfofQY5zDp+cuHc+gsEXKPpNw2KbPddreEo35O6jT6i0RVK6LhsoYBhq5TvK4/n74wnA0QbK8gGd+jUZwTMKJw==} engines: {node: '>=10'} @@ -9151,8 +9158,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.6(vue@3.3.4): - resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} + /vue-demi@0.14.7(vue@3.3.4): + resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'} hasBin: true requiresBuild: true diff --git a/src/composable/downloadBase64.ts b/src/composable/downloadBase64.ts index 37b0428d..3bc20226 100644 --- a/src/composable/downloadBase64.ts +++ b/src/composable/downloadBase64.ts @@ -1,8 +1,13 @@ -import { extension as getExtensionFromMime } from 'mime-types'; +import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types'; import type { Ref } from 'vue'; import _ from 'lodash'; -export { getMimeTypeFromBase64, useDownloadFileFromBase64 }; +export { + getMimeTypeFromBase64, + getMimeTypeFromExtension, getExtensionFromMimeType, + useDownloadFileFromBase64, useDownloadFileFromBase64Refs, + previewImageFromBase64, +}; const commonMimeTypesSignatures = { 'JVBERi0': 'application/pdf', @@ -36,30 +41,78 @@ function getFileExtensionFromMimeType({ defaultExtension?: string }) { if (mimeType) { - return getExtensionFromMime(mimeType) ?? defaultExtension; + return getExtensionFromMimeType(mimeType) ?? defaultExtension; } return defaultExtension; } -function useDownloadFileFromBase64({ source, filename }: { source: Ref; filename?: string }) { +function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }: +{ sourceValue: string; filename?: string; extension?: string; fileMimeType?: string }) { + if (sourceValue === '') { + throw new Error('Base64 string is empty'); + } + + const defaultExtension = extension ?? 'txt'; + const { mimeType } = getMimeTypeFromBase64({ base64String: sourceValue }); + let base64String = sourceValue; + if (!mimeType) { + const targetMimeType = fileMimeType ?? getMimeTypeFromExtension(defaultExtension); + base64String = `data:${targetMimeType};base64,${sourceValue}`; + } + + const cleanExtension = extension ?? getFileExtensionFromMimeType( + { mimeType, defaultExtension }); + let cleanFileName = filename ?? `file.${cleanExtension}`; + if (extension && !cleanFileName.endsWith(`.${extension}`)) { + cleanFileName = `${cleanFileName}.${cleanExtension}`; + } + + const a = document.createElement('a'); + a.href = base64String; + a.download = cleanFileName; + a.click(); +} + +function useDownloadFileFromBase64( + { source, filename, extension, fileMimeType }: + { source: Ref; filename?: string; extension?: string; fileMimeType?: string }) { return { download() { - if (source.value === '') { - throw new Error('Base64 string is empty'); - } - - const { mimeType } = getMimeTypeFromBase64({ base64String: source.value }); - const base64String = mimeType - ? source.value - : `data:text/plain;base64,${source.value}`; - - const cleanFileName = filename ?? `file.${getFileExtensionFromMimeType({ mimeType })}`; - - const a = document.createElement('a'); - a.href = base64String; - a.download = cleanFileName; - a.click(); + downloadFromBase64({ sourceValue: source.value, filename, extension, fileMimeType }); }, }; } + +function useDownloadFileFromBase64Refs( + { source, filename, extension }: + { source: Ref; filename?: Ref; extension?: Ref }) { + 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; +} diff --git a/src/tools/base64-file-converter/base64-file-converter.vue b/src/tools/base64-file-converter/base64-file-converter.vue index 377625bd..a489f9a1 100644 --- a/src/tools/base64-file-converter/base64-file-converter.vue +++ b/src/tools/base64-file-converter/base64-file-converter.vue @@ -2,12 +2,19 @@ import { useBase64 } from '@vueuse/core'; import type { Ref } from 'vue'; import { useCopy } from '@/composable/copy'; -import { useDownloadFileFromBase64 } from '@/composable/downloadBase64'; +import { getExtensionFromMimeType, getMimeTypeFromBase64, previewImageFromBase64, useDownloadFileFromBase64Refs } from '@/composable/downloadBase64'; import { useValidation } from '@/composable/validation'; import { isValidBase64 } from '@/utils/base64'; +const fileName = ref('file'); +const fileExtension = ref(''); const base64Input = ref(''); -const { download } = useDownloadFileFromBase64({ source: base64Input }); +const { download } = useDownloadFileFromBase64Refs( + { + source: base64Input, + filename: fileName, + extension: fileExtension, + }); const base64InputValidation = useValidation({ source: base64Input, rules: [ @@ -18,6 +25,35 @@ const base64InputValidation = useValidation({ ], }); +watch( + base64Input, + (newValue, _) => { + const { mimeType } = getMimeTypeFromBase64({ base64String: newValue }); + if (mimeType) { + fileExtension.value = getExtensionFromMimeType(mimeType) || fileExtension.value; + } + }, +); + +function previewImage() { + if (!base64InputValidation.isValid) { + return; + } + try { + const image = previewImageFromBase64(base64Input.value); + image.style.maxWidth = '100%'; + image.style.maxHeight = '400px'; + const previewContainer = document.getElementById('previewContainer'); + if (previewContainer) { + previewContainer.innerHTML = ''; + previewContainer.appendChild(image); + } + } + catch (_) { + // + } +} + function downloadFile() { if (!base64InputValidation.isValid) { return; @@ -44,6 +80,24 @@ async function onUpload(file: File) { From f1a5489e2194aed7619db8f95472987dad49aa0d Mon Sep 17 00:00:00 2001 From: sharevb Date: Fri, 9 Aug 2024 22:11:39 +0200 Subject: [PATCH 21/41] feat(new tools): JSON to XML and XML to JSON (#1231) * feat(new tool): JSON <> XML Fix https://github.com/CorentinTh/it-tools/issues/314 * Update src/tools/xml-to-json/index.ts * Update src/tools/json-to-xml/index.ts * Update src/tools/json-to-xml/index.ts --------- Co-authored-by: Corentin THOMASSET --- components.d.ts | 9 ++----- package.json | 1 + pnpm-lock.yaml | 35 +++++++++++++++++---------- src/tools/index.ts | 4 +++ src/tools/json-to-xml/index.ts | 12 +++++++++ src/tools/json-to-xml/json-to-xml.vue | 32 ++++++++++++++++++++++++ src/tools/xml-to-json/index.ts | 12 +++++++++ src/tools/xml-to-json/xml-to-json.vue | 32 ++++++++++++++++++++++++ 8 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 src/tools/json-to-xml/index.ts create mode 100644 src/tools/json-to-xml/json-to-xml.vue create mode 100644 src/tools/xml-to-json/index.ts create mode 100644 src/tools/xml-to-json/xml-to-json.vue diff --git a/components.d.ts b/components.d.ts index f2c3146f..1eb39cae 100644 --- a/components.d.ts +++ b/components.d.ts @@ -110,6 +110,7 @@ declare module '@vue/runtime-core' { JsonMinify: typeof import('./src/tools/json-minify/json-minify.vue')['default'] JsonToCsv: typeof import('./src/tools/json-to-csv/json-to-csv.vue')['default'] JsonToToml: typeof import('./src/tools/json-to-toml/json-to-toml.vue')['default'] + JsonToXml: typeof import('./src/tools/json-to-xml/json-to-xml.vue')['default'] JsonToYaml: typeof import('./src/tools/json-to-yaml-converter/json-to-yaml.vue')['default'] JsonViewer: typeof import('./src/tools/json-viewer/json-viewer.vue')['default'] JwtParser: typeof import('./src/tools/jwt-parser/jwt-parser.vue')['default'] @@ -130,21 +131,14 @@ declare module '@vue/runtime-core' { NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] - NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] - NFormItem: typeof import('naive-ui')['NFormItem'] - NGi: typeof import('naive-ui')['NGi'] - NGrid: typeof import('naive-ui')['NGrid'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] - NInputNumber: typeof import('naive-ui')['NInputNumber'] - NLabel: typeof import('naive-ui')['NLabel'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] NScrollbar: typeof import('naive-ui')['NScrollbar'] - NSpin: typeof import('naive-ui')['NSpin'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] @@ -186,6 +180,7 @@ declare module '@vue/runtime-core' { UuidGenerator: typeof import('./src/tools/uuid-generator/uuid-generator.vue')['default'] WifiQrCodeGenerator: typeof import('./src/tools/wifi-qr-code-generator/wifi-qr-code-generator.vue')['default'] XmlFormatter: typeof import('./src/tools/xml-formatter/xml-formatter.vue')['default'] + XmlToJson: typeof import('./src/tools/xml-to-json/xml-to-json.vue')['default'] YamlToJson: typeof import('./src/tools/yaml-to-json-converter/yaml-to-json.vue')['default'] YamlToToml: typeof import('./src/tools/yaml-to-toml/yaml-to-toml.vue')['default'] YamlViewer: typeof import('./src/tools/yaml-viewer/yaml-viewer.vue')['default'] diff --git a/package.json b/package.json index d1e6e458..180790c7 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "vue-router": "^4.1.6", "vue-tsc": "^1.8.1", "xml-formatter": "^3.3.2", + "xml-js": "^1.6.11", "yaml": "^2.2.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8619d8c0..ebe8ccbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,9 @@ dependencies: xml-formatter: specifier: ^3.3.2 version: 3.3.2 + xml-js: + specifier: ^1.6.11 + version: 1.6.11 yaml: specifier: ^2.2.1 version: 2.2.1 @@ -3354,7 +3357,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.8.0(vue@3.3.4) + '@vueuse/shared': 10.11.1(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3987,19 +3990,19 @@ packages: - vue dev: false - /@vueuse/shared@10.3.0(vue@3.3.4): - resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} + /@vueuse/shared@10.11.1(vue@3.3.4): + resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} dependencies: - vue-demi: 0.14.5(vue@3.3.4) + vue-demi: 0.14.10(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue dev: false - /@vueuse/shared@10.8.0(vue@3.3.4): - resolution: {integrity: sha512-dUdy6zwHhULGxmr9YUg8e+EnB39gcM4Fe2oKBSrh3cOsV30JcMPtsyuspgFCUo5xxFNaeMf/W2yyKfST7Bg8oQ==} + /@vueuse/shared@10.3.0(vue@3.3.4): + resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} dependencies: - vue-demi: 0.14.7(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -7959,8 +7962,6 @@ packages: /sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} requiresBuild: true - dev: true - optional: true /saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} @@ -9143,8 +9144,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.5(vue@3.3.4): - resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==} + /vue-demi@0.14.10(vue@3.3.4): + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} hasBin: true requiresBuild: true @@ -9158,8 +9159,8 @@ packages: vue: 3.3.4 dev: false - /vue-demi@0.14.7(vue@3.3.4): - resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} + /vue-demi@0.14.5(vue@3.3.4): + resolution: {integrity: sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==} engines: {node: '>=12'} hasBin: true requiresBuild: true @@ -9449,6 +9450,7 @@ packages: /workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained dependencies: workbox-background-sync: 7.0.0 workbox-core: 7.0.0 @@ -9549,6 +9551,13 @@ packages: xml-parser-xo: 4.0.5 dev: false + /xml-js@1.6.11: + resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} + hasBin: true + dependencies: + sax: 1.2.4 + dev: false + /xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} diff --git a/src/tools/index.ts b/src/tools/index.ts index aa861c93..14cf4dd6 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -6,6 +6,8 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer'; import { tool as textToUnicode } from './text-to-unicode'; import { tool as safelinkDecoder } from './safelink-decoder'; +import { tool as xmlToJson } from './xml-to-json'; +import { tool as jsonToXml } from './json-to-xml'; import { tool as pdfSignatureChecker } from './pdf-signature-checker'; import { tool as numeronymGenerator } from './numeronym-generator'; import { tool as macAddressGenerator } from './mac-address-generator'; @@ -107,6 +109,8 @@ export const toolsByCategory: ToolCategory[] = [ listConverter, tomlToJson, tomlToYaml, + xmlToJson, + jsonToXml, ], }, { diff --git a/src/tools/json-to-xml/index.ts b/src/tools/json-to-xml/index.ts new file mode 100644 index 00000000..c35ace2b --- /dev/null +++ b/src/tools/json-to-xml/index.ts @@ -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'), +}); diff --git a/src/tools/json-to-xml/json-to-xml.vue b/src/tools/json-to-xml/json-to-xml.vue new file mode 100644 index 00000000..96a7cf16 --- /dev/null +++ b/src/tools/json-to-xml/json-to-xml.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/tools/xml-to-json/index.ts b/src/tools/xml-to-json/index.ts new file mode 100644 index 00000000..8d83f4fe --- /dev/null +++ b/src/tools/xml-to-json/index.ts @@ -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'), +}); diff --git a/src/tools/xml-to-json/xml-to-json.vue b/src/tools/xml-to-json/xml-to-json.vue new file mode 100644 index 00000000..e1e5a477 --- /dev/null +++ b/src/tools/xml-to-json/xml-to-json.vue @@ -0,0 +1,32 @@ + + + From 318fb6efb99fb916aee0418b7767a05a382b6275 Mon Sep 17 00:00:00 2001 From: Corentin THOMASSET Date: Thu, 15 Aug 2024 15:29:58 +0200 Subject: [PATCH 22/41] feat(new-tool): add email normalizer (#1243) --- components.d.ts | 6 ++ package.json | 1 + pnpm-lock.yaml | 15 +++++ .../email-normalizer/email-normalizer.vue | 65 +++++++++++++++++++ src/tools/email-normalizer/index.ts | 12 ++++ src/tools/index.ts | 2 + 6 files changed, 101 insertions(+) create mode 100644 src/tools/email-normalizer/email-normalizer.vue create mode 100644 src/tools/email-normalizer/index.ts diff --git a/components.d.ts b/components.d.ts index 1eb39cae..89f41f80 100644 --- a/components.d.ts +++ b/components.d.ts @@ -72,6 +72,7 @@ declare module '@vue/runtime-core' { DockerRunToDockerComposeConverter: typeof import('./src/tools/docker-run-to-docker-compose-converter/docker-run-to-docker-compose-converter.vue')['default'] DynamicValues: typeof import('./src/tools/benchmark-builder/dynamic-values.vue')['default'] Editor: typeof import('./src/tools/html-wysiwyg-editor/editor/editor.vue')['default'] + EmailNormalizer: typeof import('./src/tools/email-normalizer/email-normalizer.vue')['default'] EmojiCard: typeof import('./src/tools/emoji-picker/emoji-card.vue')['default'] EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default'] EmojiPicker: typeof import('./src/tools/emoji-picker/emoji-picker.vue')['default'] @@ -132,13 +133,18 @@ declare module '@vue/runtime-core' { NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] + NForm: typeof import('naive-ui')['NForm'] + NFormItem: typeof import('naive-ui')['NFormItem'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] + NInputNumber: typeof import('naive-ui')['NInputNumber'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] NScrollbar: typeof import('naive-ui')['NScrollbar'] + NSlider: typeof import('naive-ui')['NSlider'] + NSwitch: typeof import('naive-ui')['NSwitch'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] diff --git a/package.json b/package.json index 180790c7..6191f702 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "crypto-js": "^4.1.1", "date-fns": "^2.29.3", "dompurify": "^3.0.6", + "email-normalizer": "^1.0.0", "emojilib": "^3.0.10", "figlet": "^1.7.0", "figue": "^1.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ebe8ccbc..3044541a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ dependencies: dompurify: specifier: ^3.0.6 version: 3.0.6 + email-normalizer: + specifier: ^1.0.0 + version: 1.0.0 emojilib: specifier: ^3.0.10 version: 3.0.10 @@ -4976,6 +4979,12 @@ packages: /electron-to-chromium@1.4.572: resolution: {integrity: sha512-RlFobl4D3ieetbnR+2EpxdzFl9h0RAJkPK3pfiwMug2nhBin2ZCsGIAJWdpNniLz43sgXam/CgipOmvTA+rUiA==} + /email-normalizer@1.0.0: + resolution: {integrity: sha512-wZYuuMtL4kUOmg/TPtCrf9hAZjbFq+FcjWA85Z5nr2lGllRnWJPxCJw3gy4Cx+adMoyVw4VJfGGvt/OHgIW+qg==} + dependencies: + typescript: 5.5.4 + dev: false + /emitter-component@1.1.1: resolution: {integrity: sha512-G+mpdiAySMuB7kesVRLuyvYRqDmshB7ReKEVuyBPkzQlmiDiLrt7hHHIy4Aff552bgknVN7B2/d3lzhGO5dvpQ==} dev: false @@ -8604,6 +8613,12 @@ packages: engines: {node: '>=14.17'} hasBin: true + /typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + dev: false + /ua-parser-js@1.0.35: resolution: {integrity: sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==} dev: false diff --git a/src/tools/email-normalizer/email-normalizer.vue b/src/tools/email-normalizer/email-normalizer.vue new file mode 100644 index 00000000..eae97c4e --- /dev/null +++ b/src/tools/email-normalizer/email-normalizer.vue @@ -0,0 +1,65 @@ + + + diff --git a/src/tools/email-normalizer/index.ts b/src/tools/email-normalizer/index.ts new file mode 100644 index 00000000..299a30f7 --- /dev/null +++ b/src/tools/email-normalizer/index.ts @@ -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'), +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index 14cf4dd6..b4c161ef 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as emailNormalizer } from './email-normalizer'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -152,6 +153,7 @@ export const toolsByCategory: ToolCategory[] = [ dockerRunToDockerComposeConverter, xmlFormatter, yamlViewer, + emailNormalizer, ], }, { From 87984e2081de4e436adac552f520c02e07e371ab Mon Sep 17 00:00:00 2001 From: sharevb Date: Sun, 25 Aug 2024 22:57:07 +0200 Subject: [PATCH 23/41] feat(new tool): Markdown to HTML (#916) * feat(new tool): Markdown to HTML Fix partially #538 * feat: add print button * Update src/tools/markdown-to-html/index.ts * Update src/tools/markdown-to-html/markdown-to-html.vue --------- Co-authored-by: Corentin THOMASSET --- components.d.ts | 2 + package.json | 2 + pnpm-lock.yaml | 61 ++++++++++++++++--- src/components/TextareaCopyable.vue | 2 + src/tools/index.ts | 2 + src/tools/markdown-to-html/index.ts | 12 ++++ .../markdown-to-html/markdown-to-html.vue | 44 +++++++++++++ 7 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 src/tools/markdown-to-html/index.ts create mode 100644 src/tools/markdown-to-html/markdown-to-html.vue diff --git a/components.d.ts b/components.d.ts index 89f41f80..8e59366a 100644 --- a/components.d.ts +++ b/components.d.ts @@ -121,6 +121,7 @@ declare module '@vue/runtime-core' { LoremIpsumGenerator: typeof import('./src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue')['default'] MacAddressGenerator: typeof import('./src/tools/mac-address-generator/mac-address-generator.vue')['default'] MacAddressLookup: typeof import('./src/tools/mac-address-lookup/mac-address-lookup.vue')['default'] + MarkdownToHtml: typeof import('./src/tools/markdown-to-html/markdown-to-html.vue')['default'] MathEvaluator: typeof import('./src/tools/math-evaluator/math-evaluator.vue')['default'] MenuBar: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar.vue')['default'] MenuBarItem: typeof import('./src/tools/html-wysiwyg-editor/editor/menu-bar-item.vue')['default'] @@ -129,6 +130,7 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] + NButton: typeof import('naive-ui')['NButton'] NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] diff --git a/package.json b/package.json index 6191f702..63e5856a 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@tiptap/pm": "2.1.6", "@tiptap/starter-kit": "2.1.6", "@tiptap/vue-3": "2.0.3", + "@types/markdown-it": "^13.0.7", "@types/figlet": "^1.5.8", "@vicons/material": "^0.12.0", "@vicons/tabler": "^0.12.0", @@ -70,6 +71,7 @@ "jwt-decode": "^3.1.2", "libphonenumber-js": "^1.10.28", "lodash": "^4.17.21", + "markdown-it": "^14.0.0", "marked": "^10.0.0", "mathjs": "^11.9.1", "mime-types": "^2.1.35", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3044541a..2311f3af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: '@types/figlet': specifier: ^1.5.8 version: 1.5.8 + '@types/markdown-it': + specifier: ^13.0.7 + version: 13.0.9 '@vicons/material': specifier: ^0.12.0 version: 0.12.0 @@ -110,6 +113,9 @@ dependencies: lodash: specifier: ^4.17.21 version: 4.17.21 + markdown-it: + specifier: ^14.0.0 + version: 14.1.0 marked: specifier: ^10.0.0 version: 10.0.0 @@ -2952,7 +2958,6 @@ packages: /@types/linkify-it@3.0.2: resolution: {integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==} - dev: true /@types/lodash-es@4.17.10: resolution: {integrity: sha512-YJP+w/2khSBwbUSFdGsSqmDvmnN3cCKoPOL7Zjle6s30ZtemkkqhjVfFqGwPN7ASil5VyjE2GtyU/yqYY6mC0A==} @@ -2974,6 +2979,13 @@ packages: '@types/mdurl': 1.0.2 dev: true + /@types/markdown-it@13.0.9: + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + dependencies: + '@types/linkify-it': 3.0.2 + '@types/mdurl': 1.0.2 + dev: false + /@types/mdast@3.0.11: resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} dependencies: @@ -2982,7 +2994,6 @@ packages: /@types/mdurl@1.0.2: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} - dev: true /@types/mime-types@2.1.1: resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==} @@ -3360,7 +3371,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.11.1(vue@3.3.4) + '@vueuse/shared': 11.0.3(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3993,19 +4004,19 @@ packages: - vue dev: false - /@vueuse/shared@10.11.1(vue@3.3.4): - resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + /@vueuse/shared@10.3.0(vue@3.3.4): + resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} dependencies: - vue-demi: 0.14.10(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue dev: false - /@vueuse/shared@10.3.0(vue@3.3.4): - resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} + /@vueuse/shared@11.0.3(vue@3.3.4): + resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==} dependencies: - vue-demi: 0.14.5(vue@3.3.4) + vue-demi: 0.14.10(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -5016,7 +5027,6 @@ packages: /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - dev: true /errno@0.1.8: resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} @@ -6688,6 +6698,12 @@ packages: dependencies: uc.micro: 1.0.6 + /linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + dependencies: + uc.micro: 2.1.0 + dev: false + /local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} engines: {node: '>=14'} @@ -6825,6 +6841,18 @@ packages: mdurl: 1.0.1 uc.micro: 1.0.6 + /markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + dev: false + /marked@10.0.0: resolution: {integrity: sha512-YiGcYcWj50YrwBgNzFoYhQ1hT6GmQbFG8SksnYJX1z4BXTHSOrz1GB5/Jm2yQvMg4nN1FHP4M6r03R10KrVUiA==} engines: {node: '>= 18'} @@ -6873,6 +6901,10 @@ packages: /mdurl@1.0.1: resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==} + /mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -7682,6 +7714,11 @@ packages: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true + /punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + dev: false + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -8626,6 +8663,10 @@ packages: /uc.micro@1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} + /uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + dev: false + /ufo@1.1.2: resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue index 8b0aae61..9585177d 100644 --- a/src/components/TextareaCopyable.vue +++ b/src/components/TextareaCopyable.vue @@ -7,6 +7,7 @@ import sqlHljs from 'highlight.js/lib/languages/sql'; import xmlHljs from 'highlight.js/lib/languages/xml'; import yamlHljs from 'highlight.js/lib/languages/yaml'; import iniHljs from 'highlight.js/lib/languages/ini'; +import markdownHljs from 'highlight.js/lib/languages/markdown'; import { useCopy } from '@/composable/copy'; const props = withDefaults( @@ -30,6 +31,7 @@ hljs.registerLanguage('html', xmlHljs); hljs.registerLanguage('xml', xmlHljs); hljs.registerLanguage('yaml', yamlHljs); hljs.registerLanguage('toml', iniHljs); +hljs.registerLanguage('markdown', markdownHljs); const { value, language, followHeightOf, copyPlacement, copyMessage } = toRefs(props); const { height } = followHeightOf.value ? useElementSize(followHeightOf) : { height: ref(null) }; diff --git a/src/tools/index.ts b/src/tools/index.ts index b4c161ef..c9003fe8 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -9,6 +9,7 @@ import { tool as textToUnicode } from './text-to-unicode'; import { tool as safelinkDecoder } from './safelink-decoder'; import { tool as xmlToJson } from './xml-to-json'; import { tool as jsonToXml } from './json-to-xml'; +import { tool as markdownToHtml } from './markdown-to-html'; import { tool as pdfSignatureChecker } from './pdf-signature-checker'; import { tool as numeronymGenerator } from './numeronym-generator'; import { tool as macAddressGenerator } from './mac-address-generator'; @@ -112,6 +113,7 @@ export const toolsByCategory: ToolCategory[] = [ tomlToYaml, xmlToJson, jsonToXml, + markdownToHtml, ], }, { diff --git a/src/tools/markdown-to-html/index.ts b/src/tools/markdown-to-html/index.ts new file mode 100644 index 00000000..73a6cfb3 --- /dev/null +++ b/src/tools/markdown-to-html/index.ts @@ -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'), +}); diff --git a/src/tools/markdown-to-html/markdown-to-html.vue b/src/tools/markdown-to-html/markdown-to-html.vue new file mode 100644 index 00000000..c84d44ec --- /dev/null +++ b/src/tools/markdown-to-html/markdown-to-html.vue @@ -0,0 +1,44 @@ + + + From 67094980c9687652c10847bf4112ae0f36a31ce9 Mon Sep 17 00:00:00 2001 From: Corentin THOMASSET Date: Fri, 13 Sep 2024 20:56:32 +0200 Subject: [PATCH 24/41] chore(readme): updated logos (#1294) --- .github/logo-dark.png | Bin 0 -> 40398 bytes .github/logo-white.png | Bin 0 -> 39632 bytes .github/logo.png | Bin 8000 -> 0 bytes README.md | 6 +++++- 4 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 .github/logo-dark.png create mode 100644 .github/logo-white.png delete mode 100644 .github/logo.png diff --git a/.github/logo-dark.png b/.github/logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9bee8a9cc024cb685e4159137bfa738b9f68a3a2 GIT binary patch literal 40398 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 UF8lQHKU$~!&Yff&zi{h+0p?k<*8l(j literal 0 HcmV?d00001 diff --git a/.github/logo-white.png b/.github/logo-white.png new file mode 100644 index 0000000000000000000000000000000000000000..560a135b652634440c8d1d76e5467546cee50b01 GIT binary patch literal 39632 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 AyZ`_I literal 0 HcmV?d00001 diff --git a/.github/logo.png b/.github/logo.png deleted file mode 100644 index 18bac95080859922223d82aac99703971011c5ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8000 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 Gzx^kVX6^<6 diff --git a/README.md b/README.md index a51f9c53..b9726864 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -![logo](.github/logo.png) + + + + logo + Useful tools for developer and people working in IT. [Have a look !](https://it-tools.tech). From f5c4ab19bc10ea468a453a1460b1de2d7ccab87b Mon Sep 17 00:00:00 2001 From: sharevb Date: Fri, 20 Sep 2024 20:39:40 +0200 Subject: [PATCH 25/41] feat(new tool): Regex Tester (and Cheatsheet) (#1030) * feat(new tool): Regex Tester Fix https://github.com/CorentinTh/it-tools/issues/1007, https://github.com/CorentinTh/it-tools/issues/991, https://github.com/CorentinTh/it-tools/issues/936, https://github.com/CorentinTh/it-tools/issues/761, https://github.com/CorentinTh/it-tools/issues/649 https://github.com/CorentinTh/it-tools/issues/644, https://github.com/CorentinTh/it-tools/issues/554 https://github.com/CorentinTh/it-tools/issues/308 * fix: refactor to service + add regex diagram + ui enhancements * fix: update queryParams * fix: deps * fix: svg style bug in @regexper/render @regexper/render use a stylesheet in svg that cause bugs in whole site. So add regexper in a shadow root * feat(new tool): added Regex Cheatsheet * Update src/tools/regex-memo/index.ts * Update src/tools/regex-tester/index.ts --------- Co-authored-by: Corentin THOMASSET --- components.d.ts | 15 +- package.json | 5 +- pnpm-lock.yaml | 46 +++++ src/composable/queryParams.ts | 33 ++- src/composable/validation.ts | 19 +- src/main.ts | 2 + src/tools/index.ts | 4 + src/tools/regex-memo/index.ts | 12 ++ src/tools/regex-memo/regex-memo.content.md | 121 +++++++++++ src/tools/regex-memo/regex-memo.vue | 32 +++ src/tools/regex-tester/index.ts | 12 ++ .../regex-tester/regex-tester.service.test.ts | 106 ++++++++++ .../regex-tester/regex-tester.service.ts | 61 ++++++ src/tools/regex-tester/regex-tester.vue | 193 ++++++++++++++++++ 14 files changed, 650 insertions(+), 11 deletions(-) create mode 100644 src/tools/regex-memo/index.ts create mode 100644 src/tools/regex-memo/regex-memo.content.md create mode 100644 src/tools/regex-memo/regex-memo.vue create mode 100644 src/tools/regex-tester/index.ts create mode 100644 src/tools/regex-tester/regex-tester.service.test.ts create mode 100644 src/tools/regex-tester/regex-tester.service.ts create mode 100644 src/tools/regex-tester/regex-tester.vue diff --git a/components.d.ts b/components.d.ts index 8e59366a..3e65c3cc 100644 --- a/components.d.ts +++ b/components.d.ts @@ -130,23 +130,19 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] - NButton: typeof import('naive-ui')['NButton'] - NCode: typeof import('naive-ui')['NCode'] + NCheckbox: typeof import('naive-ui')['NCheckbox'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NConfigProvider: typeof import('naive-ui')['NConfigProvider'] + NDivider: typeof import('naive-ui')['NDivider'] NEllipsis: typeof import('naive-ui')['NEllipsis'] - NForm: typeof import('naive-ui')['NForm'] - NFormItem: typeof import('naive-ui')['NFormItem'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] NIcon: typeof import('naive-ui')['NIcon'] - NInputNumber: typeof import('naive-ui')['NInputNumber'] NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] - NScrollbar: typeof import('naive-ui')['NScrollbar'] - NSlider: typeof import('naive-ui')['NSlider'] - NSwitch: typeof import('naive-ui')['NSwitch'] + NSpace: typeof import('naive-ui')['NSpace'] + NTable: typeof import('naive-ui')['NTable'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] @@ -156,6 +152,9 @@ declare module '@vue/runtime-core' { PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default'] QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default'] RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default'] + RegexMemo: typeof import('./src/tools/regex-memo/regex-memo.vue')['default'] + 'RegexMemo.content': typeof import('./src/tools/regex-memo/regex-memo.content.md')['default'] + RegexTester: typeof import('./src/tools/regex-tester/regex-tester.vue')['default'] ResultRow: typeof import('./src/tools/ipv4-range-expander/result-row.vue')['default'] RomanNumeralConverter: typeof import('./src/tools/roman-numeral-converter/roman-numeral-converter.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] diff --git a/package.json b/package.json index 63e5856a..f5dedc0c 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,13 @@ "dependencies": { "@it-tools/bip39": "^0.0.4", "@it-tools/oggen": "^1.3.0", + "@regexper/render": "^1.0.0", "@sindresorhus/slugify": "^2.2.1", "@tiptap/pm": "2.1.6", "@tiptap/starter-kit": "2.1.6", "@tiptap/vue-3": "2.0.3", - "@types/markdown-it": "^13.0.7", "@types/figlet": "^1.5.8", + "@types/markdown-it": "^13.0.7", "@vicons/material": "^0.12.0", "@vicons/tabler": "^0.12.0", "@vueuse/core": "^10.3.0", @@ -84,6 +85,7 @@ "pinia": "^2.0.34", "plausible-tracker": "^0.3.8", "qrcode": "^1.5.1", + "randexp": "^0.5.3", "sql-formatter": "^13.0.0", "ua-parser-js": "^1.0.35", "ulid": "^2.3.0", @@ -93,6 +95,7 @@ "vue": "^3.3.4", "vue-i18n": "^9.9.1", "vue-router": "^4.1.6", + "vue-shadow-dom": "^4.2.0", "vue-tsc": "^1.8.1", "xml-formatter": "^3.3.2", "xml-js": "^1.6.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2311f3af..e43a3217 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: '@it-tools/oggen': specifier: ^1.3.0 version: 1.3.0 + '@regexper/render': + specifier: ^1.0.0 + version: 1.0.0 '@sindresorhus/slugify': specifier: ^2.2.1 version: 2.2.1 @@ -152,6 +155,9 @@ dependencies: qrcode: specifier: ^1.5.1 version: 1.5.1 + randexp: + specifier: ^0.5.3 + version: 0.5.3 sql-formatter: specifier: ^13.0.0 version: 13.0.0 @@ -179,6 +185,9 @@ dependencies: vue-router: specifier: ^4.1.6 version: 4.1.6(vue@3.3.4) + vue-shadow-dom: + specifier: ^4.2.0 + version: 4.2.0 vue-tsc: specifier: ^1.8.1 version: 1.8.1(typescript@5.2.2) @@ -2480,6 +2489,17 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false + /@regexper/parser@1.0.0: + resolution: {integrity: sha512-S8AWIGpCNdl9PNHdbhI6TpXZsPk6FDU/RTZI+6UFF4rVFqDQKjCIbZSgFu7NihoEZKq57wKFPbbT1EzrjVvPHA==} + dev: false + + /@regexper/render@1.0.0: + resolution: {integrity: sha512-xYm9RUgnhhZotTtf8UZpK1PG2CcTRXQ3JPwfTlYUZsy2J+UcTVc7BaO/MJadpMoVuT8jrIyptH4Y0HLzqhI3hQ==} + dependencies: + '@regexper/parser': 1.0.0 + '@svgdotjs/svg.js': 3.2.4 + dev: false + /@remirror/core-constants@2.0.1: resolution: {integrity: sha512-ZR4aihtnnT9lMbhh5DEbsriJRlukRXmLZe7HmM+6ufJNNUDoazc75UX26xbgQlNUqgAqMcUdGFAnPc1JwgAdLQ==} dependencies: @@ -2629,6 +2649,10 @@ packages: string.prototype.matchall: 4.0.10 dev: true + /@svgdotjs/svg.js@3.2.4: + resolution: {integrity: sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==} + dev: false + /@tiptap/core@2.1.12(@tiptap/pm@2.1.6): resolution: {integrity: sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==} peerDependencies: @@ -4962,6 +4986,11 @@ packages: tslib: 2.5.0 dev: false + /drange@1.1.1: + resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==} + engines: {node: '>=4'} + dev: false + /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true @@ -7758,6 +7787,14 @@ packages: ret: 0.1.15 dev: false + /randexp@0.5.3: + resolution: {integrity: sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==} + engines: {node: '>=4'} + dependencies: + drange: 1.1.1 + ret: 0.2.2 + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -7928,6 +7965,11 @@ packages: engines: {node: '>=0.12'} dev: false + /ret@0.2.2: + resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} + engines: {node: '>=4'} + dev: false + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -9268,6 +9310,10 @@ packages: vue: 3.3.4 dev: false + /vue-shadow-dom@4.2.0: + resolution: {integrity: sha512-lguI064rT2HT/dxqSmXtz860KOvCq+W3nU1jMqroTmX3K1H46q22BMR4emh/Ld3ozy35XJKOaNGcr6mkJ/t/yg==} + dev: false + /vue-template-compiler@2.7.14: resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} dependencies: diff --git a/src/composable/queryParams.ts b/src/composable/queryParams.ts index 9699abbc..7cc8cc0d 100644 --- a/src/composable/queryParams.ts +++ b/src/composable/queryParams.ts @@ -1,7 +1,8 @@ import { useRouteQuery } from '@vueuse/router'; import { computed } from 'vue'; +import { useStorage } from '@vueuse/core'; -export { useQueryParam }; +export { useQueryParam, useQueryParamOrStorage }; const transformers = { number: { @@ -16,6 +17,12 @@ const transformers = { fromQuery: (value: string) => value.toLowerCase() === 'true', toQuery: (value: boolean) => (value ? 'true' : 'false'), }, + object: { + fromQuery: (value: string) => { + return JSON.parse(value); + }, + toQuery: (value: object) => JSON.stringify(value), + }, }; function useQueryParam({ name, defaultValue }: { name: string; defaultValue: T }) { @@ -33,3 +40,27 @@ function useQueryParam({ name, defaultValue }: { name: string; defaultValue: }, }); } + +function useQueryParamOrStorage({ 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; +} diff --git a/src/composable/validation.ts b/src/composable/validation.ts index 472ca4b2..33a6a7b2 100644 --- a/src/composable/validation.ts +++ b/src/composable/validation.ts @@ -3,9 +3,11 @@ import _ from 'lodash'; import { type Ref, reactive, watch } from 'vue'; type ValidatorReturnType = unknown; +type GetErrorMessageReturnType = string; export interface UseValidationRule { validator: (value: T) => ValidatorReturnType + getErrorMessage?: (value: T) => GetErrorMessageReturnType message: string } @@ -24,6 +26,15 @@ export function isFalsyOrHasThrown(cb: () => ValidatorReturnType): boolean { } } +export function getErrorMessageOrThrown(cb: () => GetErrorMessageReturnType): string { + try { + return cb() || ''; + } + catch (e: any) { + return e.toString(); + } +} + export interface ValidationAttrs { feedback: string validationStatus: string | undefined @@ -61,7 +72,13 @@ export function useValidation({ for (const rule of get(rules)) { if (isFalsyOrHasThrown(() => rule.validator(source.value))) { - state.message = rule.message; + if (rule.getErrorMessage) { + const getErrorMessage = rule.getErrorMessage; + state.message = rule.message.replace('{0}', getErrorMessageOrThrown(() => getErrorMessage(source.value))); + } + else { + state.message = rule.message; + } state.status = 'error'; } } diff --git a/src/main.ts b/src/main.ts index 36ba3b7f..19d28bf2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import { createPinia } from 'pinia'; import { createHead } from '@vueuse/head'; import { registerSW } from 'virtual:pwa-register'; +import shadow from 'vue-shadow-dom'; import { plausible } from './plugins/plausible.plugin'; import 'virtual:uno.css'; @@ -23,5 +24,6 @@ app.use(i18nPlugin); app.use(router); app.use(naive); app.use(plausible); +app.use(shadow); app.mount('#app'); diff --git a/src/tools/index.ts b/src/tools/index.ts index c9003fe8..388cfaf4 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -9,6 +9,8 @@ import { tool as textToUnicode } from './text-to-unicode'; import { tool as safelinkDecoder } from './safelink-decoder'; import { tool as xmlToJson } from './xml-to-json'; import { tool as jsonToXml } from './json-to-xml'; +import { tool as regexTester } from './regex-tester'; +import { tool as regexMemo } from './regex-memo'; import { tool as markdownToHtml } from './markdown-to-html'; import { tool as pdfSignatureChecker } from './pdf-signature-checker'; import { tool as numeronymGenerator } from './numeronym-generator'; @@ -156,6 +158,8 @@ export const toolsByCategory: ToolCategory[] = [ xmlFormatter, yamlViewer, emailNormalizer, + regexTester, + regexMemo, ], }, { diff --git a/src/tools/regex-memo/index.ts b/src/tools/regex-memo/index.ts new file mode 100644 index 00000000..f1f56489 --- /dev/null +++ b/src/tools/regex-memo/index.ts @@ -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'), +}); diff --git a/src/tools/regex-memo/regex-memo.content.md b/src/tools/regex-memo/regex-memo.content.md new file mode 100644 index 00000000..0f779401 --- /dev/null +++ b/src/tools/regex-memo/regex-memo.content.md @@ -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` +`(? +import { useThemeVars } from 'naive-ui'; +import Memo from './regex-memo.content.md'; + +const themeVars = useThemeVars(); + + + + + diff --git a/src/tools/regex-tester/index.ts b/src/tools/regex-tester/index.ts new file mode 100644 index 00000000..9cc542f0 --- /dev/null +++ b/src/tools/regex-tester/index.ts @@ -0,0 +1,12 @@ +import { Language } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Regex Tester', + path: '/regex-tester', + description: 'Regex Tester', + keywords: ['regex', 'tester', 'sample', 'expression'], + component: () => import('./regex-tester.vue'), + icon: Language, + createdAt: new Date('2024-09-20'), +}); diff --git a/src/tools/regex-tester/regex-tester.service.test.ts b/src/tools/regex-tester/regex-tester.service.test.ts new file mode 100644 index 00000000..bd4efbbc --- /dev/null +++ b/src/tools/regex-tester/regex-tester.service.test.ts @@ -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: '(.)(?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); + }); + } +}); diff --git a/src/tools/regex-tester/regex-tester.service.ts b/src/tools/regex-tester/regex-tester.service.ts new file mode 100644 index 00000000..ec8682c5 --- /dev/null +++ b/src/tools/regex-tester/regex-tester.service.ts @@ -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 = []; + 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 = []; + 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; +} diff --git a/src/tools/regex-tester/regex-tester.vue b/src/tools/regex-tester/regex-tester.vue new file mode 100644 index 00000000..a1fa7958 --- /dev/null +++ b/src/tools/regex-tester/regex-tester.vue @@ -0,0 +1,193 @@ + + + From 72517002f39d5c5ab21d8473c2fcc04f12818069 Mon Sep 17 00:00:00 2001 From: Corentin Thomasset Date: Fri, 27 Sep 2024 10:40:56 +0200 Subject: [PATCH 26/41] refactor(regex-tester): better description --- src/tools/regex-tester/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/regex-tester/index.ts b/src/tools/regex-tester/index.ts index 9cc542f0..62a5e234 100644 --- a/src/tools/regex-tester/index.ts +++ b/src/tools/regex-tester/index.ts @@ -4,7 +4,7 @@ import { defineTool } from '../tool'; export const tool = defineTool({ name: 'Regex Tester', path: '/regex-tester', - description: 'Regex Tester', + description: 'Test your regular expressions with sample text.', keywords: ['regex', 'tester', 'sample', 'expression'], component: () => import('./regex-tester.vue'), icon: Language, From 1c35ac37043f05bd9d0c7a026dd67d82aa026796 Mon Sep 17 00:00:00 2001 From: Corentin THOMASSET Date: Fri, 27 Sep 2024 14:49:11 +0200 Subject: [PATCH 27/41] docs(author): updated author links (#1316) --- README.md | 2 +- locales/de.yml | 2 +- locales/en.yml | 2 +- locales/es.yml | 6 +++--- locales/fr.yml | 2 +- locales/pt.yml | 2 +- locales/uk.yml | 2 +- locales/vi.yml | 2 +- locales/zh.yml | 2 +- package.json | 16 ++++++++-------- src/layouts/base.layout.vue | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b9726864..93c7aa73 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Big thanks to all the people who have already contributed! ## Credits -Coded with ❤️ by [Corentin Thomasset](//corentin-thomasset.fr). +Coded with ❤️ by [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=readme). This project is continuously deployed using [vercel.com](https://vercel.com). diff --git a/locales/de.yml b/locales/de.yml index 5a47c85d..e59ca114 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -38,7 +38,7 @@ about: # Über IT-Tools Diese wunderbare Website, erstellt mit ❤ von [Corentin - Thomasset](https://github.com/CorentinTh), sammelt nützliche Tools für + Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), sammelt nützliche Tools für Entwickler und Menschen, die in der IT arbeiten. Wenn du sie nützlich findest, teile sie gerne mit Personen, von denen du denkst, dass sie sie ebenfalls nützlich finden könnten, und vergiss nicht, sie in deiner diff --git a/locales/en.yml b/locales/en.yml index d09d435a..d1cd21c4 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -30,7 +30,7 @@ about: content: > # About IT-Tools - This wonderful website, made with ❤ by [Corentin Thomasset](https://github.com/CorentinTh) , aggregates useful tools for developer and people working in IT. If you find it useful, please feel free to share it to people you think may find it useful too and don't forget to bookmark it in your shortcut bar! + This wonderful website, made with ❤ by [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about) , aggregates useful tools for developer and people working in IT. If you find it useful, please feel free to share it to people you think may find it useful too and don't forget to bookmark it in your shortcut bar! IT Tools is open-source (under the MIT license) and free, and will always be, but it costs me money to host and renew the domain name. If you want to support my work, and encourage me to add more tools, please consider supporting by [sponsoring me](https://www.buymeacoffee.com/cthmsst). diff --git a/locales/es.yml b/locales/es.yml index b87502fc..2d8b2515 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -30,7 +30,7 @@ about: content: > # Sobre IT-Tools - Este maravilloso sitio web, hecho con ❤ por [Corentin Thomasset](https://github.com/CorentinTh) , agrega herramientas útiles para desarrolladores y personas que trabajan en IT. Si lo encuentra útil, no dude en compartirlo con las personas que crea que también pueden encontrarlo útil y ¡no olvide marcarlo como favorito en su barra de accesos directos! + Este maravilloso sitio web, hecho con ❤ por [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about) , agrega herramientas útiles para desarrolladores y personas que trabajan en IT. Si lo encuentra útil, no dude en compartirlo con las personas que crea que también pueden encontrarlo útil y ¡no olvide marcarlo como favorito en su barra de accesos directos! IT Tools es de código abierto (under the MIT license) y gratis, y siempre lo será, pero me cuesta dinero alojar y renovar el nombre de dominio. Si desea apoyar mi trabajo y animarme a agregar más herramientas, considere apoyarme a través de[sponsoring me](https://www.buymeacoffee.com/cthmsst). @@ -39,7 +39,7 @@ about: IT Tools está creado en Vue.js (Vue 3) con la biblioteca de componentes Naive UI y Vercel lo aloja y lo implementa continuamente. En algunas herramientas se utilizan bibliotecas de código abierto de terceros; puede encontrar la lista completa en [package.json](https://github.com/CorentinTh/it-tools/blob/main/package.json) archivo del repositorio. ## ¿Encontraste un error? ¿Falta una herramienta? - + Si necesita una herramienta que actualmente no está presente aquí y cree que puede ser útil, puede enviar una solicitud de función en el [issues section](https://github.com/CorentinTh/it-tools/issues/new/choose) en el repositorio de GitHub. Y si encontró un error o algo no funciona como se esperaba, presente un reporte de error en el [issues section](https://github.com/CorentinTh/it-tools/issues/new/choose) en el repositorio de GitHub. @@ -68,4 +68,4 @@ tools: math: Math measurement: Measurement text: Text - data: Data \ No newline at end of file + data: Data diff --git a/locales/fr.yml b/locales/fr.yml index 35c7df2e..cd5cc0e8 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -29,7 +29,7 @@ about: content: > # À propos de IT-Tools - Ce merveilleux site, fait avec ❤ par [Corentin Thomasset](https://github.com/CorentinTh), regroupe des outils utiles pour les développeurs et les personnes travaillant dans l'informatique. Si vous le trouvez utile, n'hésitez pas à le partager et n'oubliez pas de le mettre dans vos favoris ! + Ce merveilleux site, fait avec ❤ par [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), regroupe des outils utiles pour les développeurs et les personnes travaillant dans l'informatique. Si vous le trouvez utile, n'hésitez pas à le partager et n'oubliez pas de le mettre dans vos favoris ! IT Tools est open-source (sous licence MIT) et gratuit, et le restera toujours, mais cela me coûte de l'argent pour l'héberger et renouveler le nom de domaine. Si vous voulez soutenir mon travail, et m'encourager à ajouter plus d'outils, n'hésitez pas à me [soutenir](https://www.buymeacoffee.com/cthmsst). diff --git a/locales/pt.yml b/locales/pt.yml index 96fdaed4..822bea8a 100644 --- a/locales/pt.yml +++ b/locales/pt.yml @@ -30,7 +30,7 @@ about: content: > # Sobre o IT-Tools - Este site maravilhoso, feito com ❤ por [Corentin Thomasset](https://github.com/CorentinTh), junta ferramentas úteis para desenvolvedores e outras pessoas que trabalham com TI. Se você achar o site útil, fique à vontade para compartilhar com quem também possa gostar e não esqueça de salvar o bookmark na sua barra de atalhos! + Este site maravilhoso, feito com ❤ por [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), junta ferramentas úteis para desenvolvedores e outras pessoas que trabalham com TI. Se você achar o site útil, fique à vontade para compartilhar com quem também possa gostar e não esqueça de salvar o bookmark na sua barra de atalhos! O IT Tools é código aberto (sob a licença MIT), é gratuito, e sempre será, mas custa dinheiro para hospedar e renovar o domínio. Se quiser apoiar meu trabalho e me encorajar a adicionar mais ferramentas, por favor considere [ser patrocinador](https://www.buymeacoffee.com/cthmsst). diff --git a/locales/uk.yml b/locales/uk.yml index 2d28f157..ad455a7d 100644 --- a/locales/uk.yml +++ b/locales/uk.yml @@ -30,7 +30,7 @@ about: content: > # Про IT-Tools - Цей чудовий вебсайт, створений з ❤ [Corentin Thomasset](https://github.com/CorentinTh), агрегує корисні інструменти для розробників і людей, які працюють в сфері IT. Якщо вам це корисно, будь ласка, поділіться цим з людьми, які, на вашу думку, також можуть знайти його корисним, і не забудьте додати його до закладок у вашій панелі швидкого доступу! + Цей чудовий вебсайт, створений з ❤ [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), агрегує корисні інструменти для розробників і людей, які працюють в сфері IT. Якщо вам це корисно, будь ласка, поділіться цим з людьми, які, на вашу думку, також можуть знайти його корисним, і не забудьте додати його до закладок у вашій панелі швидкого доступу! IT Tools є відкритим програмним забезпеченням (під ліцензією MIT) і безкоштовним, і завжди буде таким, але мені коштує гроші для хостингу і продовження доменного імені. Якщо ви хочете підтримати мою роботу і підтримати мене у додаванні нових інструментів, розгляньте можливість підтримки, [спонсоруючи мене](https://www.buymeacoffee.com/cthmsst). diff --git a/locales/vi.yml b/locales/vi.yml index 9eb16bf0..de574a6c 100644 --- a/locales/vi.yml +++ b/locales/vi.yml @@ -30,7 +30,7 @@ about: content: > # Về IT-Tools - Website tuyệt vời này, được tạo ra bằng ❤ bởi [Corentin Thomasset](https://github.com/CorentinTh), tổng hợp các công cụ hữu ích cho nhà phát triển và những người làm việc trong lĩnh vực IT. Nếu bạn thấy nó hữu ích, xin đừng ngần ngại chia sẻ cho những người mà bạn nghĩ sẽ thấy nó hữu ích và đừng quên đánh dấu nó trong thanh lối tắt của bạn! + Website tuyệt vời này, được tạo ra bằng ❤ bởi [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about), tổng hợp các công cụ hữu ích cho nhà phát triển và những người làm việc trong lĩnh vực IT. Nếu bạn thấy nó hữu ích, xin đừng ngần ngại chia sẻ cho những người mà bạn nghĩ sẽ thấy nó hữu ích và đừng quên đánh dấu nó trong thanh lối tắt của bạn! IT Tools là mã nguồn mở (dưới giấy phép MIT) và miễn phí, và sẽ luôn như vậy, nhưng tôi phải trả tiền để lưu trữ và gia hạn tên miền. Nếu bạn muốn hỗ trợ công việc của tôi, và khích lệ tôi thêm nhiều công cụ hơn, hãy xem xét hỗ trợ bằng cách [tài trợ cho tôi](https://www.buymeacoffee.com/cthmsst). diff --git a/locales/zh.yml b/locales/zh.yml index 160fe1fa..9b065682 100644 --- a/locales/zh.yml +++ b/locales/zh.yml @@ -30,7 +30,7 @@ about: content: > # 关于 IT-Tools - IT-Tools 由 [Corentin Thomasset](https://github.com/CorentinTh) 用 ❤ 开发,汇集了对开发人员和 IT 从业者有用的工具。如果对您有帮助,请将其分享给您的朋友,并且添加到收藏夹中! + IT-Tools 由 [Corentin Thomasset](https://corentin.tech?utm_source=it-tools&utm_medium=about) 用 ❤ 开发,汇集了对开发人员和 IT 从业者有用的工具。如果对您有帮助,请将其分享给您的朋友,并且添加到收藏夹中! IT-Tools 永久免费且开源(MIT 许可证),但需要资金用于托管和续订域名。如果您想支持我的工作,并鼓励我添加更多工具,请考虑通过 [赞助我](https://www.buymeacoffee.com/cthmsst) 进行支持。 diff --git a/package.json b/package.json index f5dedc0c..8a1c069f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,14 @@ { "name": "it-tools", "version": "2024.5.13-a0bc346", + "packageManager": "pnpm@8.15.3", "description": "Collection of handy online tools for developers, with great UX. ", + "author": "Corentin Th (https://corentin.tech)", + "license": "GNU GPLv3", + "repository": { + "type": "git", + "url": "https://github.com/CorentinTh/it-tools" + }, "keywords": [ "productivity", "converter", @@ -13,12 +20,6 @@ "developer-tools", "developer-productivity" ], - "author": "Corentin Th (https://github.com/CorentinTh)", - "license": "GNU GPLv3", - "repository": { - "type": "git", - "url": "https://github.com/CorentinTh/it-tools" - }, "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && NODE_OPTIONS=--max_old_space_size=4096 vite build", @@ -145,6 +146,5 @@ "vitest": "^0.34.0", "workbox-window": "^7.0.0", "zx": "^7.2.1" - }, - "packageManager": "pnpm@8.15.3" + } } diff --git a/src/layouts/base.layout.vue b/src/layouts/base.layout.vue index 62ac5113..ef534209 100644 --- a/src/layouts/base.layout.vue +++ b/src/layouts/base.layout.vue @@ -81,7 +81,7 @@ const tools = computed(() => [
© {{ new Date().getFullYear() }} - + Corentin Thomasset
From f962c416a33e1481fd7d1f4eca927bbf5810cde7 Mon Sep 17 00:00:00 2001 From: Corentin THOMASSET Date: Thu, 3 Oct 2024 00:01:09 +0200 Subject: [PATCH 28/41] chore(sponsors): fern sponsor banners (#1314) * chore(sponsors): readme banner * chore(sponsors): app sponsor --- .github/fern-banner.svg | 11 +++++++++++ README.md | 4 ++++ src/config.ts | 6 ++++++ src/pages/Home.page.vue | 30 ++++++++++++++++++++++++++++-- 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 .github/fern-banner.svg diff --git a/.github/fern-banner.svg b/.github/fern-banner.svg new file mode 100644 index 00000000..20b918f7 --- /dev/null +++ b/.github/fern-banner.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/README.md b/README.md index 93c7aa73..dfe7f3d4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Useful tools for developer and people working in IT. [Have a look !](https://it-tools.tech). +## Sponsors + +[![Fern banner](./.github/fern-banner.svg)](https://bit.ly/3zBl7DG) + ## Functionalities and roadmap Please check the [issues](https://github.com/CorentinTh/it-tools/issues) to see if some feature listed to be implemented. diff --git a/src/config.ts b/src/config.ts index 2558d038..fa2421ef 100644 --- a/src/config.ts +++ b/src/config.ts @@ -59,6 +59,12 @@ export const config = figue({ default: false, env: 'VITE_SHOW_BANNER', }, + showSponsorBanner: { + doc: 'Show the sponsor banner', + format: 'boolean', + default: false, + env: 'VITE_SHOW_SPONSOR_BANNER', + }, }) .loadEnv({ ...import.meta.env, diff --git a/src/pages/Home.page.vue b/src/pages/Home.page.vue index dfd12b60..190601c1 100644 --- a/src/pages/Home.page.vue +++ b/src/pages/Home.page.vue @@ -15,8 +15,8 @@ const { t } = useI18n();