-
+ |
{{ claim }}
@@ -49,7 +49,7 @@ const validation = useValidation({
({{ claimDescription }})
|
-
+ |
{{ value }}
({{ friendlyValue }})
diff --git a/src/tools/lorem-ipsum-generator/locales/en.yml b/src/tools/lorem-ipsum-generator/locales/en.yml
index 2a8c762a..255b73cd 100644
--- a/src/tools/lorem-ipsum-generator/locales/en.yml
+++ b/src/tools/lorem-ipsum-generator/locales/en.yml
@@ -12,3 +12,4 @@ tools:
copyBtn: Copy
copied: Lorem ipsum copied to the clipboard
+ refresh: Refresh
diff --git a/src/tools/lorem-ipsum-generator/locales/fr.yml b/src/tools/lorem-ipsum-generator/locales/fr.yml
index e591c413..6f222efd 100644
--- a/src/tools/lorem-ipsum-generator/locales/fr.yml
+++ b/src/tools/lorem-ipsum-generator/locales/fr.yml
@@ -12,3 +12,4 @@ tools:
copyBtn: Copier
copied: Lorem ipsum copié dans le presse-papiers
+ refresh: Rafraîchir
diff --git a/src/tools/lorem-ipsum-generator/locales/zh.yml b/src/tools/lorem-ipsum-generator/locales/zh.yml
index 3951709f..0f033dbf 100644
--- a/src/tools/lorem-ipsum-generator/locales/zh.yml
+++ b/src/tools/lorem-ipsum-generator/locales/zh.yml
@@ -12,3 +12,4 @@ tools:
copyBtn: 复制
copied: Lorem Ipsum 已复制到剪贴板
+ refresh: 刷新
diff --git a/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue b/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue
index de9096bb..560a7941 100644
--- a/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue
+++ b/src/tools/lorem-ipsum-generator/lorem-ipsum-generator.vue
@@ -2,6 +2,7 @@
import { generateLoremIpsum } from './lorem-ipsum-generator.service';
import { useCopy } from '@/composable/copy';
import { randIntFromInterval } from '@/utils/random';
+import { computedRefreshable } from '@/composable/computedRefreshable';
const { t } = useI18n();
const paragraphs = ref(1);
@@ -10,7 +11,7 @@ const words = ref([8, 15]);
const startWithLoremIpsum = ref(true);
const asHTML = ref(false);
-const loremIpsumText = computed(() =>
+const [loremIpsumText, refreshLoremIpsum] = computedRefreshable(() =>
generateLoremIpsum({
paragraphCount: paragraphs.value,
asHTML: asHTML.value,
@@ -42,10 +43,13 @@ const { copy } = useCopy({ source: loremIpsumText, text: t('tools.lorem-ipsum-ge
-
+
{{ t('tools.lorem-ipsum-generator.copyBtn') }}
+
+ {{ t('tools.lorem-ipsum-generator.refresh') }}
+
diff --git a/src/tools/markdown-to-html/index.ts b/src/tools/markdown-to-html/index.ts
new file mode 100644
index 00000000..0160efd8
--- /dev/null
+++ b/src/tools/markdown-to-html/index.ts
@@ -0,0 +1,13 @@
+import { Markdown } from '@vicons/tabler';
+import { defineTool } from '../tool';
+import { translate as t } from '@/plugins/i18n.plugin';
+
+export const tool = defineTool({
+ name: t('tools.markdown-to-html.title'),
+ path: '/markdown-to-html',
+ description: t('tools.markdown-to-html.description'),
+ 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/locales/en.yml b/src/tools/markdown-to-html/locales/en.yml
new file mode 100644
index 00000000..a8d8070d
--- /dev/null
+++ b/src/tools/markdown-to-html/locales/en.yml
@@ -0,0 +1,10 @@
+tools:
+ markdown-to-html:
+ title: Markdown to HTML
+ description: Convert Markdown to Html and allow to print (as PDF).
+
+ inputLabel: 'Your Markdown to convert:'
+ inputPlaceholder: 'Your Markdown content...'
+ outputLabel: 'Output HTML:'
+
+ printAsPDF: 'Print as PDF'
diff --git a/src/tools/markdown-to-html/locales/fr.yml b/src/tools/markdown-to-html/locales/fr.yml
new file mode 100644
index 00000000..df5410db
--- /dev/null
+++ b/src/tools/markdown-to-html/locales/fr.yml
@@ -0,0 +1,10 @@
+tools:
+ markdown-to-html:
+ title: Markdown vers HTML
+ description: Convertir Markdown en Html et permettre l'impression (en PDF).
+
+ inputLabel: 'Votre Markdown à convertir:'
+ inputPlaceholder: 'Votre contenu Markdown...'
+ outputLabel: 'HTML de sortie:'
+
+ printAsPDF: 'Imprimer en PDF'
diff --git a/src/tools/markdown-to-html/locales/zh.yml b/src/tools/markdown-to-html/locales/zh.yml
new file mode 100644
index 00000000..fc4fb8a8
--- /dev/null
+++ b/src/tools/markdown-to-html/locales/zh.yml
@@ -0,0 +1,10 @@
+tools:
+ markdown-to-html:
+ title: Markdown 转 HTML
+ description: '将 Markdown 转换为 Html 并允许打印(作为 PDF)。'
+
+ inputLabel: '要转换的 Markdown:'
+ inputPlaceholder: '您的 Markdown 内容...'
+ outputLabel: '输出 HTML:'
+
+ printAsPDF: '作为 PDF 打印'
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..9f22185c
--- /dev/null
+++ b/src/tools/markdown-to-html/markdown-to-html.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('tools.markdown-to-html.printAsPDF') }}
+
+
+
+
diff --git a/src/tools/regex-memo/index.ts b/src/tools/regex-memo/index.ts
new file mode 100644
index 00000000..b9e74e46
--- /dev/null
+++ b/src/tools/regex-memo/index.ts
@@ -0,0 +1,13 @@
+import { BrandJavascript } from '@vicons/tabler';
+import { defineTool } from '../tool';
+import { translate as t } from '@/plugins/i18n.plugin';
+
+export const tool = defineTool({
+ name: t('tools.regex-memo.title'),
+ path: '/regex-memo',
+ description: t('tools.regex-memo.description'),
+ 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/locales/en.yml b/src/tools/regex-memo/locales/en.yml
new file mode 100644
index 00000000..ca44c7f8
--- /dev/null
+++ b/src/tools/regex-memo/locales/en.yml
@@ -0,0 +1,4 @@
+tools:
+ regex-memo:
+ title: Regex cheatsheet
+ description: Javascript Regex/Regular Expression cheatsheet.
diff --git a/src/tools/regex-memo/locales/fr.yml b/src/tools/regex-memo/locales/fr.yml
new file mode 100644
index 00000000..5407e639
--- /dev/null
+++ b/src/tools/regex-memo/locales/fr.yml
@@ -0,0 +1,4 @@
+tools:
+ regex-memo:
+ title: Feuille de triche Regex
+ description: Feuille de triche Javascript Regex/Expression régulière.
diff --git a/src/tools/regex-memo/locales/zh.yml b/src/tools/regex-memo/locales/zh.yml
new file mode 100644
index 00000000..a839d041
--- /dev/null
+++ b/src/tools/regex-memo/locales/zh.yml
@@ -0,0 +1,4 @@
+tools:
+ regex-memo:
+ title: 正则表达式速查表
+ description: Javascript 正则表达式速查表。
diff --git a/src/tools/regex-memo/regex-memo.content.fr.md b/src/tools/regex-memo/regex-memo.content.fr.md
new file mode 100644
index 00000000..591cc4f5
--- /dev/null
+++ b/src/tools/regex-memo/regex-memo.content.fr.md
@@ -0,0 +1,121 @@
+### Caractères normaux
+
+Expression | Description
+:--|:--
+`.` ou `[^\n\r]` | tout caractère *sauf* un saut de ligne ou retour chariot
+`[A-Za-z]` | alphabet
+`[a-z]` | alphabet minuscule
+`[A-Z]` | alphabet majuscule
+`\d` ou `[0-9]` | chiffre
+`\D` ou `[^0-9]` | non-chiffre
+`_` | soulignement
+`\w` ou `[A-Za-z0-9_]` | alphabet, chiffre ou soulignement
+`\W` ou `[^A-Za-z0-9_]` | inverse de `\w`
+`\S` | inverse de `\s`
+
+### Caractères d'espace
+
+Expression | Description
+:--|:--
+` ` | espace
+`\t` | tabulation
+`\n` | saut de ligne
+`\r` | retour chariot
+`\s` | espace, tabulation, saut de ligne ou retour chariot
+
+### Ensemble de caractères
+
+Expression | Description
+:--|:--
+`[xyz]` | soit `x`, `y` ou `z`
+`[^xyz]` | ni `x`, ni `y`, ni `z`
+`[1-3]` | soit `1`, `2` ou `3`
+`[^1-3]` | ni `1`, ni `2`, ni `3`
+
+- Pensez à un ensemble de caractères comme une opération `OU` sur les caractères simples qui sont enfermés entre crochets.
+- Utilisez `^` après le `[` d'ouverture pour "négation" de l'ensemble de caractères.
+- Dans un ensemble de caractères, `.` signifie un point littéral.
+
+### Caractères nécessitant un échappement
+
+#### En dehors d'un ensemble de caractères
+
+Expression | Description
+:--|:--
+`\.` | point
+`\^` | accent circonflexe
+`\$` | signe dollar
+`\|` | barre verticale
+`\\` | barre oblique inverse
+`\/` | barre oblique
+`\(` | parenthèse ouvrante
+`\)` | parenthèse fermante
+`\[` | crochet ouvrant
+`\]` | crochet fermant
+`\{` | accolade ouvrante
+`\}` | accolade fermante
+
+#### À l'intérieur d'un ensemble de caractères
+
+Expression | Description
+:--|:--
+`\\` | barre oblique inverse
+`\]` | crochet fermant
+
+- Un `^` doit être échappé uniquement s'il se produit immédiatement après le `[` d'ouverture de l'ensemble de caractères.
+- Un `-` doit être échappé uniquement s'il se produit entre deux alphabets ou deux chiffres.
+
+### Quantificateurs
+
+Expression | Description
+:--|:--
+`{2}` | exactement 2
+`{2,}` | au moins 2
+`{2,7}` | au moins 2 mais pas plus de 7
+`*` | 0 ou plus
+`+` | 1 ou plus
+`?` | exactement 0 ou 1
+
+- Le quantificateur va *après* l'expression à quantifier.
+
+### Limites
+
+Expression | Description
+:--|:--
+`^` | début de chaîne
+`$` | fin de chaîne
+`\b` | limite de mot
+
+- Comment fonctionne la correspondance des limites de mots :
+ - Au début de la chaîne si le premier caractère est `\w`.
+ - Entre deux caractères adjacents dans la chaîne, si le premier caractère est `\w` et le deuxième caractère est `\W`.
+ - À la fin de la chaîne si le dernier caractère est `\w`.
+
+### Correspondance
+
+Expression | Description
+:--|:--
+`foo\|bar` | correspond soit à `foo` soit à `bar`
+`foo(?=bar)` | correspond à `foo` s'il est avant `bar`
+`foo(?!bar)` | correspond à `foo` s'il n'est *pas* avant `bar`
+`(?<=bar)foo` | correspond à `foo` s'il est après `bar`
+`(?
+import { useThemeVars } from 'naive-ui';
+import Memo from './regex-memo.content.md';
+import MemoZH from './regex-memo.content.zh.md';
+import MemoFR from './regex-memo.content.fr.md';
+
+const themeVars = useThemeVars();
+const { locale } = useI18n();
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/regex-tester/index.ts b/src/tools/regex-tester/index.ts
new file mode 100644
index 00000000..a8861e21
--- /dev/null
+++ b/src/tools/regex-tester/index.ts
@@ -0,0 +1,13 @@
+import { Language } from '@vicons/tabler';
+import { defineTool } from '../tool';
+import { translate as t } from '@/plugins/i18n.plugin';
+
+export const tool = defineTool({
+ name: t('tools.regex-tester.title'),
+ path: '/regex-tester',
+ description: t('tools.regex-tester.description'),
+ 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/locales/en.yml b/src/tools/regex-tester/locales/en.yml
new file mode 100644
index 00000000..b0532efa
--- /dev/null
+++ b/src/tools/regex-tester/locales/en.yml
@@ -0,0 +1,36 @@
+tools:
+ regex-tester:
+ title: Regex Tester
+ description: Test your regular expressions with sample text.
+
+ inputTitle: 'Regex'
+ inputLabel: 'Regex to test:'
+ inputPlaceholder: 'Put the regex to test'
+ outputLabel: 'Text to match:'
+ outputPlaceholder: 'Put the text to match'
+
+ helpTip: 'See Regular Expression Cheatsheet'
+ globalTitle: 'Global search'
+ globalLabel: 'Global search.'
+ ignoreCaseTitle: 'Case-insensitive search'
+ ignoreCaseLabel: 'Case-insensitive search.'
+ multilineTitle: 'Allows ^ and $ to match next to newline characters.'
+ multilineLabel: 'Multiline'
+ dotAllTitle: 'Allows . to match newline characters.'
+ dotAllLabel: 'Singleline'
+ unicodeTitle: 'Unicode; treat a pattern as a sequence of Unicode code points.'
+ unicodeLabel: 'Unicode'
+ unicodeSetsTitle: 'An upgrade to the u mode with more Unicode features.'
+ unicodeSetsLabel: 'Unicode Sets'
+
+ matches: 'Matches'
+ index: 'Index in text'
+ value: 'Value'
+ captures: 'Captures'
+ groups: 'Groups'
+ noMatch: 'No match'
+
+ sampleMatchingText: 'Sample matching text'
+ regexDiagram: 'Regex Diagram'
+
+ invalidRegex: 'Invalid regex: {0}'
diff --git a/src/tools/regex-tester/locales/fr.yml b/src/tools/regex-tester/locales/fr.yml
new file mode 100644
index 00000000..683cce88
--- /dev/null
+++ b/src/tools/regex-tester/locales/fr.yml
@@ -0,0 +1,36 @@
+tools:
+ regex-tester:
+ title: Testeur Regex
+ description: Testez vos expressions régulières avec un texte d'exemple.
+
+ inputTitle: 'Regex'
+ inputLabel: 'Regex à tester:'
+ inputPlaceholder: 'Mettez le regex à tester'
+ outputLabel: 'Texte à faire correspondre:'
+ outputPlaceholder: 'Mettez le texte à faire correspondre'
+
+ helpTip: 'Voir la feuille de triche des expressions régulières'
+ globalTitle: 'Recherche globale'
+ globalLabel: 'Recherche globale.'
+ ignoreCaseTitle: 'Recherche insensible à la casse'
+ ignoreCaseLabel: 'Recherche insensible à la casse.'
+ multilineTitle: 'Permet à ^ et $ de correspondre aux caractères de nouvelle ligne.'
+ multilineLabel: 'Multiligne'
+ dotAllTitle: 'Permet à . de correspondre aux caractères de nouvelle ligne.'
+ dotAllLabel: 'Monoligne'
+ unicodeTitle: 'Unicode ; traiter un modèle comme une séquence de points de code Unicode.'
+ unicodeLabel: 'Unicode'
+ unicodeSetsTitle: 'Une mise à niveau du mode u avec plus de fonctionnalités Unicode.'
+ unicodeSetsLabel: 'Ensembles Unicode'
+
+ matches: 'Correspondances'
+ index: 'Index dans le texte'
+ value: 'Valeur'
+ captures: 'Captures'
+ groups: 'Groupes'
+ noMatch: 'Aucune correspondance'
+
+ sampleMatchingText: "Texte de correspondance d'exemple"
+ regexDiagram: 'Schéma Regex'
+
+ invalidRegex: 'Regex invalide: {0}'
diff --git a/src/tools/regex-tester/locales/zh.yml b/src/tools/regex-tester/locales/zh.yml
new file mode 100644
index 00000000..3a908909
--- /dev/null
+++ b/src/tools/regex-tester/locales/zh.yml
@@ -0,0 +1,36 @@
+tools:
+ regex-tester:
+ title: 正则表达式测试工具
+ description: 使用示例文本测试您的正则表达式。
+
+ inputTitle: '正则表达式'
+ inputLabel: '要测试的正则表达式:'
+ inputPlaceholder: '输入要测试的正则表达式'
+ outputLabel: '匹配的文本:'
+ outputPlaceholder: '输入要匹配的文本'
+
+ helpTip: '查看正则表达式速查表'
+ globalTitle: '全局搜索'
+ globalLabel: '全局搜索。'
+ ignoreCaseTitle: '不区分大小写搜索'
+ ignoreCaseLabel: '不区分大小写搜索。'
+ multilineTitle: '允许^和$匹配换行符旁边的字符。'
+ multilineLabel: '多行'
+ dotAllTitle: '允许.匹配换行符。'
+ dotAllLabel: '单行'
+ unicodeTitle: 'Unicode; 将模式视为 Unicode 代码点序列。'
+ unicodeLabel: 'Unicode'
+ unicodeSetsTitle: '具有更多 Unicode 功能的 u 模式升级版。'
+ unicodeSetsLabel: 'Unicode 集'
+
+ matches: '匹配项'
+ index: '文本中的索引'
+ value: '值'
+ captures: '捕获'
+ groups: '分组'
+ noMatch: '无匹配项'
+
+ sampleMatchingText: '示例匹配文本'
+ regexDiagram: '正则表达式图解'
+
+ invalidRegex: '无效的正则表达式:{0}'
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..f6dee686
--- /dev/null
+++ b/src/tools/regex-tester/regex-tester.vue
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+ {{ t('tools.regex-tester.helpTip') }}
+
+
+
+ {{ t('tools.regex-tester.globalLabel') }} (g )
+
+
+ {{ t('tools.regex-tester.ignoreCaseLabel') }} (i )
+
+
+ {{ t('tools.regex-tester.multilineLabel') }}(m )
+
+
+ {{ t('tools.regex-tester.dotAllLabel') }}(s )
+
+
+ {{ t('tools.regex-tester.unicodeLabel') }}(u )
+
+
+ {{ t('tools.regex-tester.unicodeSetsLabel') }} (v )
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('tools.regex-tester.index') }}
+ |
+
+ {{ t('tools.regex-tester.value') }}
+ |
+
+ {{ t('tools.regex-tester.captures') }}
+ |
+
+ {{ t('tools.regex-tester.groups') }}
+ |
+
+
+
+
+ {{ match.index }} |
+ {{ match.value }} |
+
+
+ -
+ "{{ capture.name }}" = {{ capture.value }} [{{ capture.start }} - {{ capture.end }}]
+
+
+ |
+
+
+ -
+ "{{ group.name }}" = {{ group.value }} [{{ group.start }} - {{ group.end }}]
+
+
+ |
+
+
+
+
+ {{ t('tools.regex-tester.noMatch') }}
+
+
+
+
+ {{ sample }}
+
+
+
+
+
+
+
+
+
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);
}
diff --git a/src/tools/url-encoder/url-encoder.vue b/src/tools/url-encoder/url-encoder.vue
index caa117a5..6c3a0523 100644
--- a/src/tools/url-encoder/url-encoder.vue
+++ b/src/tools/url-encoder/url-encoder.vue
@@ -25,7 +25,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)),
diff --git a/src/tools/xml-to-json/index.ts b/src/tools/xml-to-json/index.ts
new file mode 100644
index 00000000..a037b703
--- /dev/null
+++ b/src/tools/xml-to-json/index.ts
@@ -0,0 +1,13 @@
+import { Braces } from '@vicons/tabler';
+import { defineTool } from '../tool';
+import { translate as t } from '@/plugins/i18n.plugin';
+
+export const tool = defineTool({
+ name: t('tools.xml-to-json.title'),
+ path: '/xml-to-json',
+ description: t('tools.xml-to-json.description'),
+ 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/locales/en.yml b/src/tools/xml-to-json/locales/en.yml
new file mode 100644
index 00000000..d84d8213
--- /dev/null
+++ b/src/tools/xml-to-json/locales/en.yml
@@ -0,0 +1,10 @@
+tools:
+ xml-to-json:
+ title: XML to JSON
+ description: Convert XML to JSON.
+
+ inputLabel: Your XML content
+ inputPlaceholder: Paste your XML content here...
+ outputLabel: Converted JSON
+
+ invalidMessage: Provided XML is not valid.
diff --git a/src/tools/xml-to-json/locales/fr.yml b/src/tools/xml-to-json/locales/fr.yml
new file mode 100644
index 00000000..502490b6
--- /dev/null
+++ b/src/tools/xml-to-json/locales/fr.yml
@@ -0,0 +1,10 @@
+tools:
+ xml-to-json:
+ title: XML vers JSON
+ description: Convertir XML en JSON.
+
+ inputLabel: Votre contenu XML
+ inputPlaceholder: Collez votre contenu XML ici...
+ outputLabel: JSON converti
+
+ invalidMessage: Le XML fourni n'est pas valide.
diff --git a/src/tools/xml-to-json/locales/zh.yml b/src/tools/xml-to-json/locales/zh.yml
new file mode 100644
index 00000000..51412cd2
--- /dev/null
+++ b/src/tools/xml-to-json/locales/zh.yml
@@ -0,0 +1,10 @@
+tools:
+ xml-to-json:
+ title: XML 转 JSON
+ description: 将 XML 转换为 JSON。
+
+ inputLabel: 您的 XML 内容
+ inputPlaceholder: 在此粘贴您的 XML 内容...
+ outputLabel: 转换后的 JSON
+
+ invalidMessage: 提供的 XML 不是有效的。
\ No newline at end of file
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..9c148595
--- /dev/null
+++ b/src/tools/xml-to-json/xml-to-json.vue
@@ -0,0 +1,33 @@
+
+
+
+
+
diff --git a/src/utils/base64.test.ts b/src/utils/base64.test.ts
index 994f1b1b..51d15239 100644
--- a/src/utils/base64.test.ts
+++ b/src/utils/base64.test.ts
@@ -38,7 +38,8 @@ describe('base64 utils', () => {
it('should throw for incorrect base64 string', () => {
expect(() => base64ToText('a')).to.throw('Incorrect base64 string');
- expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
+ // should not really be false because trimming of space is now implied
+ // expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
expect(() => base64ToText('é')).to.throw('Incorrect base64 string');
// missing final '='
expect(() => base64ToText('bG9yZW0gaXBzdW0')).to.throw('Incorrect base64 string');
@@ -56,17 +57,17 @@ describe('base64 utils', () => {
it('should return false for incorrect base64 string', () => {
expect(isValidBase64('a')).to.eql(false);
- expect(isValidBase64(' ')).to.eql(false);
expect(isValidBase64('é')).to.eql(false);
expect(isValidBase64('data:text/plain;notbase64,YQ==')).to.eql(false);
// missing final '='
expect(isValidBase64('bG9yZW0gaXBzdW0')).to.eql(false);
});
- it('should return false for untrimmed correct base64 string', () => {
- expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(false);
- expect(isValidBase64(' LTE=')).to.eql(false);
- expect(isValidBase64(' YQ== ')).to.eql(false);
+ it('should return true for untrimmed correct base64 string', () => {
+ expect(isValidBase64('bG9yZW0gaXBzdW0= ')).to.eql(true);
+ expect(isValidBase64(' LTE=')).to.eql(true);
+ expect(isValidBase64(' YQ== ')).to.eql(true);
+ expect(isValidBase64(' ')).to.eql(true);
});
});
diff --git a/src/utils/base64.ts b/src/utils/base64.ts
index 16912ee3..44e59f41 100644
--- a/src/utils/base64.ts
+++ b/src/utils/base64.ts
@@ -1,7 +1,9 @@
+import { Base64 } from 'js-base64';
+
export { textToBase64, base64ToText, isValidBase64, removePotentialDataAndMimePrefix };
function textToBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boolean } = {}) {
- const encoded = window.btoa(str);
+ const encoded = Base64.encode(str);
return makeUrlSafe ? makeUriSafe(encoded) : encoded;
}
@@ -16,7 +18,7 @@ function base64ToText(str: string, { makeUrlSafe = false }: { makeUrlSafe?: bool
}
try {
- return window.atob(cleanStr);
+ return Base64.decode(cleanStr);
}
catch (_) {
throw new Error('Incorrect base64 string');
@@ -34,10 +36,11 @@ function isValidBase64(str: string, { makeUrlSafe = false }: { makeUrlSafe?: boo
}
try {
+ const reEncodedBase64 = Base64.fromUint8Array(Base64.toUint8Array(cleanStr));
if (makeUrlSafe) {
- return removePotentialPadding(window.btoa(window.atob(cleanStr))) === cleanStr;
+ return removePotentialPadding(reEncodedBase64) === cleanStr;
}
- return window.btoa(window.atob(cleanStr)) === cleanStr;
+ return reEncodedBase64 === cleanStr.replace(/\s/g, '');
}
catch (err) {
return false;
|