diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d5c67208..4dd9ffb7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,7 +15,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/e2e-tests.yml b/.github/workflows/e2e-tests.yml
index b5b04096..13b787ef 100644
--- a/.github/workflows/e2e-tests.yml
+++ b/.github/workflows/e2e-tests.yml
@@ -18,7 +18,7 @@ jobs:
- uses: actions/setup-node@v3
with:
- node-version: 16
+ node-version: 20
cache: 'pnpm'
- name: Get Playwright version
diff --git a/components.d.ts b/components.d.ts
index fabbe793..e31119b3 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -12,6 +12,7 @@ declare module '@vue/runtime-core' {
'404.page': typeof import('./src/pages/404.page.vue')['default']
About: typeof import('./src/pages/About.vue')['default']
App: typeof import('./src/App.vue')['default']
+ AsciiTextDrawer: typeof import('./src/tools/ascii-text-drawer/ascii-text-drawer.vue')['default']
'Base.layout': typeof import('./src/layouts/base.layout.vue')['default']
Base64FileConverter: typeof import('./src/tools/base64-file-converter/base64-file-converter.vue')['default']
Base64StringConverter: typeof import('./src/tools/base64-string-converter/base64-string-converter.vue')['default']
@@ -88,28 +89,17 @@ declare module '@vue/runtime-core' {
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default']
'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default']
- 'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default']
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
- IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default']
- IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
- IconMdiCamera: typeof import('~icons/mdi/camera')['default']
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
- IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
- IconMdiDownload: typeof import('~icons/mdi/download')['default']
IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiHeart: typeof import('~icons/mdi/heart')['default']
- IconMdiPause: typeof import('~icons/mdi/pause')['default']
- IconMdiPlay: typeof import('~icons/mdi/play')['default']
- IconMdiRecord: typeof import('~icons/mdi/record')['default']
- IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiSearch: typeof import('~icons/mdi/search')['default']
IconMdiTranslate: typeof import('~icons/mdi/translate')['default']
IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default']
- IconMdiVideo: typeof import('~icons/mdi/video')['default']
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default']
@@ -136,39 +126,25 @@ declare module '@vue/runtime-core' {
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
- NAlert: typeof import('naive-ui')['NAlert']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
- NCheckbox: typeof import('naive-ui')['NCheckbox']
NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
- NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
- NDatePicker: typeof import('naive-ui')['NDatePicker']
NDivider: typeof import('naive-ui')['NDivider']
- NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEllipsis: typeof import('naive-ui')['NEllipsis']
- NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NH1: typeof import('naive-ui')['NH1']
- NH2: typeof import('naive-ui')['NH2']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
- NImage: typeof import('naive-ui')['NImage']
- NInputGroup: typeof import('naive-ui')['NInputGroup']
- NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
NInputNumber: typeof import('naive-ui')['NInputNumber']
+ NLabel: typeof import('naive-ui')['NLabel']
NLayout: typeof import('naive-ui')['NLayout']
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu']
- NProgress: typeof import('naive-ui')['NProgress']
NScrollbar: typeof import('naive-ui')['NScrollbar']
- NSlider: typeof import('naive-ui')['NSlider']
- NStatistic: typeof import('naive-ui')['NStatistic']
- NSwitch: typeof import('naive-ui')['NSwitch']
- NTable: typeof import('naive-ui')['NTable']
- NTag: typeof import('naive-ui')['NTag']
+ 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']
diff --git a/package.json b/package.json
index a5380242..23a0c6c1 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"@tiptap/starter-kit": "2.1.6",
"@tiptap/vue-3": "2.0.3",
"@types/turndown": "^5.0.4",
+ "@types/figlet": "^1.5.8",
"@vicons/material": "^0.12.0",
"@vicons/tabler": "^0.12.0",
"@vueuse/core": "^10.3.0",
@@ -58,6 +59,7 @@
"date-fns": "^2.29.3",
"dompurify": "^3.0.6",
"emojilib": "^3.0.10",
+ "figlet": "^1.7.0",
"figue": "^1.2.0",
"fuse.js": "^6.6.2",
"highlight.js": "^11.7.0",
diff --git a/playwright.config.ts b/playwright.config.ts
index 3caa0612..5257c526 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -9,7 +9,7 @@ const useWebServer = process.env.NO_WEB_SERVER !== 'true';
*/
export default defineConfig({
testDir: './src',
- testMatch: /.*\.e2e\.(spec\.)?ts/,
+ testMatch: /\.e2e\.(spec\.)?ts$/,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
@@ -57,7 +57,7 @@ export default defineConfig({
&& {
webServer: {
command: 'npm run preview',
- url: 'http://127.0.0.1:5050',
+ url: 'http://localhost:5050',
reuseExistingServer: !isCI,
},
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 44b636fa..bd4eab2e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ dependencies:
'@types/turndown':
specifier: ^5.0.4
version: 5.0.4
+ '@types/figlet':
+ specifier: ^1.5.8
+ version: 1.5.8
'@vicons/material':
specifier: ^0.12.0
version: 0.12.0
@@ -74,6 +77,9 @@ dependencies:
emojilib:
specifier: ^3.0.10
version: 3.0.10
+ figlet:
+ specifier: ^1.7.0
+ version: 1.7.0
figue:
specifier: ^1.2.0
version: 1.2.0
@@ -2908,6 +2914,10 @@ packages:
/@types/estree@1.0.0:
resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
+ /@types/figlet@1.5.8:
+ resolution: {integrity: sha512-G22AUvy4Tl95XLE7jmUM8s8mKcoz+Hr+Xm9W90gJsppJq9f9tHvOGkrpn4gRX0q/cLtBdNkWtWCKDg2UDZoZvQ==}
+ dev: false
+
/@types/fs-extra@11.0.1:
resolution: {integrity: sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==}
dependencies:
@@ -5599,6 +5609,12 @@ packages:
web-streams-polyfill: 3.2.1
dev: true
+ /figlet@1.7.0:
+ resolution: {integrity: sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==}
+ engines: {node: '>= 0.4.0'}
+ hasBin: true
+ dev: false
+
/figue@1.2.0:
resolution: {integrity: sha512-CXKr12kiNWjKtUK3X+YHeXKepn80s9Rg6pgZXoLQYEybgwaGJ9uGW4DrBrVK30ZWZf1mcvTbXF56AcovG7gLVw==}
dependencies:
diff --git a/src/tools/ascii-text-drawer/ascii-text-drawer.vue b/src/tools/ascii-text-drawer/ascii-text-drawer.vue
new file mode 100644
index 00000000..9a6520a4
--- /dev/null
+++ b/src/tools/ascii-text-drawer/ascii-text-drawer.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading font...
+
+
+
+ Current settings resulted in error.
+
+
+
+
+
+
+
diff --git a/src/tools/ascii-text-drawer/index.ts b/src/tools/ascii-text-drawer/index.ts
new file mode 100644
index 00000000..cc1ba86c
--- /dev/null
+++ b/src/tools/ascii-text-drawer/index.ts
@@ -0,0 +1,12 @@
+import { Artboard } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'ASCII Art Text Generator',
+ path: '/ascii-text-drawer',
+ description: 'Create ASCII art text with many fonts and styles.',
+ keywords: ['ascii', 'asciiart', 'text', 'drawer'],
+ component: () => import('./ascii-text-drawer.vue'),
+ icon: Artboard,
+ createdAt: new Date('2024-03-03'),
+});
diff --git a/src/tools/bcrypt/bcrypt.vue b/src/tools/bcrypt/bcrypt.vue
index c28c20bf..d4881299 100644
--- a/src/tools/bcrypt/bcrypt.vue
+++ b/src/tools/bcrypt/bcrypt.vue
@@ -28,7 +28,7 @@ const compareMatch = computed(() => compareSync(compareString.value, compareHash
mb-2
/>
-
+
diff --git a/src/tools/index.ts b/src/tools/index.ts
index aa2418b2..02f40a47 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -1,7 +1,11 @@
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 asciiTextDrawer } from './ascii-text-drawer';
+
import { tool as textToUnicode } from './text-to-unicode';
+import { tool as safelinkDecoder } from './safelink-decoder';
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
import { tool as numeronymGenerator } from './numeronym-generator';
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -125,6 +129,7 @@ export const toolsByCategory: ToolCategory[] = [
userAgentParser,
httpStatusCodes,
jsonDiff,
+ safelinkDecoder,
],
},
{
@@ -161,7 +166,15 @@ export const toolsByCategory: ToolCategory[] = [
},
{
name: 'Text',
- components: [loremIpsumGenerator, textStatistics, emojiPicker, stringObfuscator, textDiff, numeronymGenerator],
+ components: [
+ loremIpsumGenerator,
+ textStatistics,
+ emojiPicker,
+ stringObfuscator,
+ textDiff,
+ numeronymGenerator,
+ asciiTextDrawer,
+ ],
},
{
name: 'Data',
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';
}
diff --git a/src/tools/safelink-decoder/index.ts b/src/tools/safelink-decoder/index.ts
new file mode 100644
index 00000000..ef865108
--- /dev/null
+++ b/src/tools/safelink-decoder/index.ts
@@ -0,0 +1,12 @@
+import { Mailbox } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'Outlook Safelink decoder',
+ path: '/safelink-decoder',
+ description: 'Decode Outlook SafeLink links',
+ keywords: ['outlook', 'safelink', 'decoder'],
+ component: () => import('./safelink-decoder.vue'),
+ icon: Mailbox,
+ createdAt: new Date('2024-03-11'),
+});
diff --git a/src/tools/safelink-decoder/safelink-decoder.service.test.ts b/src/tools/safelink-decoder/safelink-decoder.service.test.ts
new file mode 100644
index 00000000..b601f01e
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.service.test.ts
@@ -0,0 +1,21 @@
+import { describe, expect, it } from 'vitest';
+import { decodeSafeLinksURL } from './safelink-decoder.service';
+
+describe('safelink-decoder', () => {
+ describe('decodeSafeLinksURL', () => {
+ describe('decode outlook safelink urls', () => {
+ it('should decode basic safelink urls', () => {
+ expect(decodeSafeLinksURL('https://aus01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3Dsafelink%26rlz%3D1&data=05%7C02%7C%7C1ed07253975b46da1d1508dc3443752a%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638442711583216725%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=%2BQY0HBnnxfI7pzZoxzlhZdDvYu80LwQB0zUUjrffVnk%3D&reserved=0'))
+ .toBe('https://www.google.com/search?q=safelink&rlz=1');
+ });
+ it('should decode encoded safelink urls', () => {
+ expect(decodeSafeLinksURL('https://aus01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.google.com%2Fsearch%3Fq%3Dsafelink%26rlz%3D1&data=05%7C02%7C%7C1ed07253975b46da1d1508dc3443752a%7C84df9e7fe9f640afb435aaaaaaaaaaaa%7C1%7C0%7C638442711583216725%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C0%7C%7C%7C&sdata=%2BQY0HBnnxfI7pzZoxzlhZdDvYu80LwQB0zUUjrffVnk%3D&reserved=0'))
+ .toBe('https://www.google.com/search?q=safelink&rlz=1');
+ });
+ it('throw on not outlook safelink urls', () => {
+ expect(() => decodeSafeLinksURL('https://google.com'))
+ .toThrow('Invalid SafeLinks URL provided');
+ });
+ });
+ });
+});
diff --git a/src/tools/safelink-decoder/safelink-decoder.service.ts b/src/tools/safelink-decoder/safelink-decoder.service.ts
new file mode 100644
index 00000000..96be00ab
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.service.ts
@@ -0,0 +1,7 @@
+export function decodeSafeLinksURL(safeLinksUrl: string) {
+ if (!safeLinksUrl.match(/\.safelinks\.protection\.outlook\.com/)) {
+ throw new Error('Invalid SafeLinks URL provided');
+ }
+
+ return new URL(safeLinksUrl).searchParams.get('url');
+}
diff --git a/src/tools/safelink-decoder/safelink-decoder.vue b/src/tools/safelink-decoder/safelink-decoder.vue
new file mode 100644
index 00000000..01337eb2
--- /dev/null
+++ b/src/tools/safelink-decoder/safelink-decoder.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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)),