mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-11-01 12:33:43 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf5e4d9056 | ||
|
|
992f96b48a | ||
|
|
fcf4cfe64d | ||
|
|
f54223fb0a | ||
|
|
b38ab82d05 | ||
|
|
f6cd9b76d3 | ||
|
|
208a373fd0 | ||
|
|
8089c60000 | ||
|
|
d30cd8a9ab | ||
|
|
04a8e122be | ||
|
|
447bdf2148 | ||
|
|
ca7cb44389 | ||
|
|
e48d60b1ed | ||
|
|
fda0b0ca25 |
@@ -7,15 +7,29 @@ module.exports = {
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'@vue/eslint-config-typescript/recommended',
|
||||
'@vue/eslint-config-prettier',
|
||||
'plugin:import/recommended',
|
||||
],
|
||||
|
||||
settings: {
|
||||
'import/resolver': { typescript: { project: './tsconfig.app.json' } },
|
||||
},
|
||||
env: {
|
||||
'vue/setup-compiler-macros': true,
|
||||
},
|
||||
rules: {
|
||||
'vue/multi-word-component-names': ['off'],
|
||||
'prettier/prettier': ['error'],
|
||||
'import/no-duplicates': ['error', { considerQueryString: true }],
|
||||
'import/order': ['error', { groups: [['builtin', 'external', 'internal']] }],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{
|
||||
js: 'never',
|
||||
ts: 'never',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -2,6 +2,39 @@
|
||||
|
||||
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.
|
||||
|
||||
### [2.10.3](https://github.com/CorentinTh/it-tools/compare/v2.10.2...v2.10.3) (2022-08-14)
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **share:** new share banner ([fcf4cfe](https://github.com/CorentinTh/it-tools/commit/fcf4cfe64d4c1c3814137c8ff23b83a1ca0d502d))
|
||||
* **share:** updated twitter meta tags ([992f96b](https://github.com/CorentinTh/it-tools/commit/992f96b48a89e2793ccf75fb9e28b2ec7b7f62b6))
|
||||
* **validation:** simplified validation management with helpers ([f54223f](https://github.com/CorentinTh/it-tools/commit/f54223fb0aaedbd101b5d3dc4176053533bb936a))
|
||||
|
||||
### [2.10.2](https://github.com/CorentinTh/it-tools/compare/v2.10.1...v2.10.2) (2022-08-04)
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **dry:** mutualised duplicated code with withDefaultOnError ([f6cd9b7](https://github.com/CorentinTh/it-tools/commit/f6cd9b76d38800e1a1f63d07152fc96cda562795))
|
||||
* **home:** removed new tool first sort ([d30cd8a](https://github.com/CorentinTh/it-tools/commit/d30cd8a9abc3298c0a0b05f249e54318bb4537f2))
|
||||
* **json-prettifier:** more permissive json parser ([8089c60](https://github.com/CorentinTh/it-tools/commit/8089c60000000c42c821c6586c128d3d2b248885))
|
||||
* **lint:** added import rules ([208a373](https://github.com/CorentinTh/it-tools/commit/208a373fd08ac550778745eb6e4536bf02537da7))
|
||||
|
||||
### [2.10.1](https://github.com/CorentinTh/it-tools/compare/v2.10.0...v2.10.1) (2022-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bip39-generator:** cleared an issue with the mnemonic validation ([ca7cb44](https://github.com/CorentinTh/it-tools/commit/ca7cb4438972ca09f28a6a40332ec94ceaa4aab4))
|
||||
* **import:** removed auto added weird .js extension ([fda0b0c](https://github.com/CorentinTh/it-tools/commit/fda0b0ca25c1733542a4e797ac1a2150c546a660))
|
||||
|
||||
|
||||
### Refactors
|
||||
|
||||
* **base64:** mutualized base64 functions into global utilities ([447bdf2](https://github.com/CorentinTh/it-tools/commit/447bdf2148098d70ba309e13d9b1e846b5064da1))
|
||||
* **chronometer:** improved chronometer precision ([e48d60b](https://github.com/CorentinTh/it-tools/commit/e48d60b1ed19279f48441743f7ed69e8fd915011))
|
||||
|
||||
## [2.10.0](https://github.com/CorentinTh/it-tools/compare/v2.9.2...v2.10.0) (2022-08-03)
|
||||
|
||||
|
||||
|
||||
12
index.html
12
index.html
@@ -31,17 +31,21 @@
|
||||
property="og:description"
|
||||
content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
|
||||
/>
|
||||
<meta property="og:image" content="/banner.png" />
|
||||
<meta property="og:image" content="https://it-tools.tech/banner.png" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="it-tools.tech" />
|
||||
<meta property="twitter:url" content="https://it-tools.tech/" />
|
||||
<meta name="twitter:domain" content="it-tools.tech" />
|
||||
<meta name="twitter:url" content="https://it-tools.tech/" />
|
||||
<meta name="twitter:site" content="@ittoolsdottech" />
|
||||
<meta name="twitter:creator" content="@cthmsst" />
|
||||
|
||||
<meta name="twitter:title" content="IT Tools - Handy online tools for developers" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Collection of handy online tools for developers, with great UX. IT Tools is a free and open-source collection of handy online tools for developers & people working in IT."
|
||||
/>
|
||||
<meta name="twitter:image" content="/banner.png" />
|
||||
<meta name="twitter:image" content="https://it-tools.tech/banner.png" />
|
||||
<meta name="twitter:image:alt" content="IT Tools - Handy online tools for developers" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
808
package-lock.json
generated
808
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "it-tools",
|
||||
"version": "2.10.0",
|
||||
"version": "2.10.3",
|
||||
"description": "Collection of handy online tools for developers, with great UX. ",
|
||||
"keywords": [
|
||||
"productivity",
|
||||
@@ -68,6 +68,7 @@
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/randombytes": "^2.0.0",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/parser": "^5.32.0",
|
||||
"@vitejs/plugin-vue": "^2.2.2",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.7",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
@@ -77,6 +78,8 @@
|
||||
"c8": "^7.11.0",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.4.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"less": "^4.1.2",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 155 KiB |
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { layouts } from './layouts';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, RouterView } from 'vue-router';
|
||||
import { darkThemeOverrides, lightThemeOverrides } from './themes';
|
||||
import { darkTheme, NGlobalStyle, NMessageProvider } from 'naive-ui';
|
||||
import { darkThemeOverrides, lightThemeOverrides } from './themes';
|
||||
import { layouts } from './layouts';
|
||||
import { useStyleStore } from './stores/style.store';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
@@ -4,14 +4,14 @@ import { h } from 'vue';
|
||||
import { RouterLink, useRoute } from 'vue-router';
|
||||
import { Heart, Menu2, Home2 } from '@vicons/tabler';
|
||||
import { toolsByCategory } from '@/tools';
|
||||
import SearchBar from '../components/SearchBar.vue';
|
||||
import { useStyleStore } from '@/stores/style.store';
|
||||
import HeroGradient from '../assets/hero-gradient.svg?component';
|
||||
import MenuLayout from '../components/MenuLayout.vue';
|
||||
import NavbarButtons from '../components/NavbarButtons.vue';
|
||||
import { config } from '@/config';
|
||||
import MenuIconItem from '@/components/MenuIconItem.vue';
|
||||
import type { ITool } from '@/tools/tool';
|
||||
import SearchBar from '../components/SearchBar.vue';
|
||||
import HeroGradient from '../assets/hero-gradient.svg?component';
|
||||
import MenuLayout from '../components/MenuLayout.vue';
|
||||
import NavbarButtons from '../components/NavbarButtons.vue';
|
||||
|
||||
const themeVars = useThemeVars();
|
||||
const route = useRoute();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import BaseLayout from './base.layout.vue';
|
||||
import { useHead } from '@vueuse/head';
|
||||
import type { HeadObject } from '@vueuse/head';
|
||||
import { computed } from 'vue';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import BaseLayout from './base.layout.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const theme = useThemeVars();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createHead } from '@vueuse/head';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { registerSW } from 'virtual:pwa-register';
|
||||
import { plausible } from './plugins/plausible.plugin';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { toolsWithCategory } from '@/tools';
|
||||
import ToolCard from '../components/ToolCard.vue';
|
||||
import { useHead } from '@vueuse/head';
|
||||
import ToolCard from '../components/ToolCard.vue';
|
||||
|
||||
useHead({ title: 'IT Tools - Handy online tools for developers' });
|
||||
</script>
|
||||
@@ -9,13 +9,7 @@ useHead({ title: 'IT Tools - Handy online tools for developers' });
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<n-grid x-gap="12" y-gap="12" cols="1 400:2 800:3 1200:4 2000:8">
|
||||
<n-gi
|
||||
v-for="tool in [
|
||||
...toolsWithCategory.filter(({ isNew }) => isNew),
|
||||
...toolsWithCategory.filter(({ isNew }) => !isNew),
|
||||
]"
|
||||
:key="tool.name"
|
||||
>
|
||||
<n-gi v-for="tool in toolsWithCategory" :key="tool.name">
|
||||
<tool-card :tool="tool" />
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { layouts } from './layouts/index';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import { layouts } from './layouts/index';
|
||||
import HomePage from './pages/Home.page.vue';
|
||||
import NotFound from './pages/404.page.vue';
|
||||
import { tools } from './tools';
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { isValidBase64 } from '@/utils/base64';
|
||||
import { Upload } from '@vicons/tabler';
|
||||
import { useBase64 } from '@vueuse/core';
|
||||
import type { UploadFileInfo } from 'naive-ui';
|
||||
@@ -47,7 +48,7 @@ const base64InputValidation = useValidation({
|
||||
rules: [
|
||||
{
|
||||
message: 'Invalid base 64 string',
|
||||
validator: (value) => window.atob(value.replace(/^data:.*?;base64,/, '')),
|
||||
validator: (value) => isValidBase64(value.trim()),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -37,24 +37,20 @@
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { base64ToText, isValidBase64, textToBase64 } from '@/utils/base64';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const textInput = ref('');
|
||||
const base64Output = computed(() => window.btoa(textInput.value));
|
||||
const base64Output = computed(() => textToBase64(textInput.value));
|
||||
const { copy: copyTextBase64 } = useCopy({ source: base64Output, text: 'Base64 string copied to the clipboard' });
|
||||
|
||||
const base64Input = ref('');
|
||||
const textOutput = computed(() => {
|
||||
try {
|
||||
return window.atob(base64Input.value);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const textOutput = computed(() => withDefaultOnError(() => base64ToText(base64Input.value.trim()), ''));
|
||||
const { copy: copyText } = useCopy({ source: textOutput, text: 'String copied to the clipboard' });
|
||||
const b64Validation = useValidation({
|
||||
source: base64Input,
|
||||
rules: [{ message: 'Invalid base64 string', validator: (value) => window.atob(value) }],
|
||||
rules: [{ message: 'Invalid base64 string', validator: (value) => isValidBase64(value.trim()) }],
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -30,11 +30,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { textToBase64 } from '@/utils/base64';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
const header = computed(() => `Authorization: Basic ${window.btoa(`${username.value}:${password.value}`)}`);
|
||||
const header = computed(() => `Authorization: Basic ${textToBase64(`${username.value}:${password.value}`)}`);
|
||||
|
||||
const { copy } = useCopy({ source: header, text: 'Header copied to the clipboard' });
|
||||
</script>
|
||||
|
||||
@@ -59,24 +59,26 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { isNotThrowing } from '@/utils/boolean';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import {
|
||||
entropyToMnemonic,
|
||||
englishWordList,
|
||||
chineseSimplifiedWordList,
|
||||
chineseTraditionalWordList,
|
||||
czechWordList,
|
||||
englishWordList,
|
||||
entropyToMnemonic,
|
||||
frenchWordList,
|
||||
generateEntropy,
|
||||
italianWordList,
|
||||
japaneseWordList,
|
||||
koreanWordList,
|
||||
mnemonicToEntropy,
|
||||
portugueseWordList,
|
||||
spanishWordList,
|
||||
generateEntropy,
|
||||
mnemonicToEntropy,
|
||||
} from '@it-tools/bip39';
|
||||
import { Copy, Refresh } from '@vicons/tabler';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const languages = {
|
||||
English: englishWordList,
|
||||
@@ -97,20 +99,11 @@ const passphraseInput = ref('');
|
||||
const language = ref<keyof typeof languages>('English');
|
||||
const passphrase = computed({
|
||||
get() {
|
||||
try {
|
||||
return entropyToMnemonic(entropy.value, languages[language.value]);
|
||||
} catch (_) {
|
||||
return passphraseInput.value;
|
||||
}
|
||||
return withDefaultOnError(() => entropyToMnemonic(entropy.value, languages[language.value]), passphraseInput.value);
|
||||
},
|
||||
set(value: string) {
|
||||
passphraseInput.value = value;
|
||||
|
||||
try {
|
||||
entropy.value = mnemonicToEntropy(value, languages[language.value]);
|
||||
} catch (_) {
|
||||
entropy.value = '';
|
||||
}
|
||||
entropy.value = withDefaultOnError(() => mnemonicToEntropy(value, languages[language.value]), '');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -132,14 +125,7 @@ const mnemonicValidation = useValidation({
|
||||
source: passphrase,
|
||||
rules: [
|
||||
{
|
||||
validator: (value) => {
|
||||
try {
|
||||
mnemonicToEntropy(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
validator: (value) => isNotThrowing(() => mnemonicToEntropy(value, languages[language.value])),
|
||||
message: 'Invalid mnemonic',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -46,8 +46,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
|
||||
import {
|
||||
camelCase,
|
||||
capitalCase,
|
||||
@@ -61,6 +59,7 @@ import {
|
||||
sentenceCase,
|
||||
snakeCase,
|
||||
} from 'change-case';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
|
||||
const input = ref('lorem ipsum dolor sit amet');
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { formatChronometerTime } from './chronometer.service';
|
||||
import { formatMs } from './chronometer.service';
|
||||
|
||||
describe('chronometer', () => {
|
||||
describe('formatChronometerTime', () => {
|
||||
it('format the elapsed time', () => {
|
||||
expect(formatChronometerTime({ elapsed: 123456 })).toEqual('02:03.456');
|
||||
expect(formatChronometerTime({ elapsed: 123456, msPerUnit: 100 })).toEqual('03:25:45.600');
|
||||
expect(formatChronometerTime({ elapsed: 12345600 })).toEqual('03:25:45.600');
|
||||
expect(formatMs(0)).toEqual('00:00.000');
|
||||
expect(formatMs(1)).toEqual('00:00.001');
|
||||
expect(formatMs(123456)).toEqual('02:03.456');
|
||||
expect(formatMs(12345600)).toEqual('03:25:45.600');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
export function formatChronometerTime({ elapsed, msPerUnit = 1 }: { elapsed: number; msPerUnit?: number }) {
|
||||
const elapsedMs = elapsed * msPerUnit;
|
||||
|
||||
const ms = elapsedMs % 1000;
|
||||
const secs = ((elapsedMs - ms) / 1000) % 60;
|
||||
const mins = (((elapsedMs - ms) / 1000 - secs) / 60) % 60;
|
||||
const hrs = (((elapsedMs - ms) / 1000 - secs) / 60 - mins) / 60;
|
||||
export function formatMs(msTotal: number) {
|
||||
const ms = msTotal % 1000;
|
||||
const secs = ((msTotal - ms) / 1000) % 60;
|
||||
const mins = (((msTotal - ms) / 1000 - secs) / 60) % 60;
|
||||
const hrs = (((msTotal - ms) / 1000 - secs) / 60 - mins) / 60;
|
||||
const hrsString = hrs > 0 ? `${hrs.toString().padStart(2, '0')}:` : '';
|
||||
|
||||
return `${hrsString}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card>
|
||||
<div class="duration">{{ formatChronometerTime({ elapsed: counter, msPerUnit }) }}</div>
|
||||
<div class="duration">{{ formatMs(counter) }}</div>
|
||||
</n-card>
|
||||
<br />
|
||||
<n-space justify="center">
|
||||
<n-button v-if="!isActive" secondary type="primary" @click="resume">Start</n-button>
|
||||
<n-button v-if="!isRunning" secondary type="primary" @click="resume">Start</n-button>
|
||||
<n-button v-else secondary type="warning" @click="pause">Stop</n-button>
|
||||
|
||||
<n-button secondary @click="counter = 0">Reset</n-button>
|
||||
@@ -14,12 +14,33 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useInterval } from '@vueuse/core';
|
||||
import { formatChronometerTime } from './chronometer.service';
|
||||
import { useRafFn } from '@vueuse/core';
|
||||
import { ref } from 'vue';
|
||||
import { formatMs } from './chronometer.service';
|
||||
|
||||
const msPerUnit = 10;
|
||||
const isRunning = ref(false);
|
||||
const counter = ref(0);
|
||||
|
||||
const { counter, pause, resume, isActive } = useInterval(msPerUnit, { controls: true, immediate: false });
|
||||
let previousRafDate = Date.now();
|
||||
const { pause: pauseRaf, resume: resumeRaf } = useRafFn(
|
||||
() => {
|
||||
const deltaMs = Date.now() - previousRafDate;
|
||||
previousRafDate = Date.now();
|
||||
counter.value += deltaMs;
|
||||
},
|
||||
{ immediate: false },
|
||||
);
|
||||
|
||||
function resume() {
|
||||
previousRafDate = Date.now();
|
||||
resumeRaf();
|
||||
isRunning.value = true;
|
||||
}
|
||||
|
||||
function pause() {
|
||||
pauseRaf();
|
||||
isRunning.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -36,12 +36,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { colord, extend } from 'colord';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
|
||||
import cmykPlugin from 'colord/plugins/cmyk';
|
||||
import hwbPlugin from 'colord/plugins/hwb';
|
||||
import namesPlugin from 'colord/plugins/names';
|
||||
import lchPlugin from 'colord/plugins/lch';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
|
||||
extend([cmykPlugin, hwbPlugin, namesPlugin, lchPlugin]);
|
||||
|
||||
|
||||
@@ -51,7 +51,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Duplicate issue with sub directory
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
import { addMilliseconds, formatRelative } from 'date-fns';
|
||||
// eslint-disable-next-line import/no-duplicates
|
||||
import { enGB } from 'date-fns/locale';
|
||||
import { computed, ref } from 'vue';
|
||||
import { formatMsDuration } from './eta-calculator.service';
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Memo from './git-memo.md';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import Memo from './git-memo.md';
|
||||
|
||||
const themeVars = useThemeVars();
|
||||
</script>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/composable/copy.js';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import {
|
||||
enc,
|
||||
HmacMD5,
|
||||
@@ -63,7 +63,7 @@ import {
|
||||
lib,
|
||||
} from 'crypto-js';
|
||||
import { computed, ref } from 'vue';
|
||||
import { convertHexToBin } from '../hash-text/hash-text.service.js';
|
||||
import { convertHexToBin } from '../hash-text/hash-text.service';
|
||||
|
||||
const algos = {
|
||||
MD5: HmacMD5,
|
||||
|
||||
@@ -67,9 +67,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useStyleStore } from '@/stores/style.store';
|
||||
import { convertBase } from './integer-base-converter.model';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
import { useStyleStore } from '@/stores/style.store';
|
||||
|
||||
const styleStore = useStyleStore();
|
||||
|
||||
|
||||
@@ -24,25 +24,21 @@
|
||||
<script setup lang="ts">
|
||||
import TextareaCopyable from '@/components/TextareaCopyable.vue';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import JSON5 from 'json5';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const inputElement = ref<HTMLElement>();
|
||||
|
||||
const rawJson = ref('{"hello": "world"}');
|
||||
const cleanJson = computed(() => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(rawJson.value), null, 3);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const cleanJson = computed(() => withDefaultOnError(() => JSON.stringify(JSON5.parse(rawJson.value), null, 3), ''));
|
||||
|
||||
const rawJsonValidation = useValidation({
|
||||
source: rawJson,
|
||||
rules: [
|
||||
{
|
||||
validator: (v) => v === '' || JSON.parse(v),
|
||||
message: 'Invalid json',
|
||||
validator: (v) => v === '' || JSON5.parse(v),
|
||||
message: 'Provided JSON is not valid.',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { ref, computed } from 'vue';
|
||||
import { generateLoremIpsum } from './lorem-ipsum-generator.service';
|
||||
import { randIntFromInterval } from '@/utils/random';
|
||||
import { generateLoremIpsum } from './lorem-ipsum-generator.service';
|
||||
|
||||
const paragraphs = ref(1);
|
||||
const sentences = ref([3, 8]);
|
||||
|
||||
@@ -21,18 +21,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { evaluate } from 'mathjs';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const expression = ref('');
|
||||
|
||||
const result = computed(() => {
|
||||
try {
|
||||
return evaluate(expression.value) ?? '';
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const result = computed(() => withDefaultOnError(() => evaluate(expression.value) ?? '', ''));
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
|
||||
import { useQRCode } from './useQRCode';
|
||||
import { ref } from 'vue';
|
||||
import type { QRCodeErrorCorrectionLevel } from 'qrcode';
|
||||
import { useQRCode } from './useQRCode';
|
||||
|
||||
const foreground = ref('#000000ff');
|
||||
const background = ref('#ffffffff');
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
<script setup lang="ts">
|
||||
import TextareaCopyable from '@/components/TextareaCopyable.vue';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64.js';
|
||||
import { useDownloadFileFromBase64 } from '@/composable/downloadBase64';
|
||||
import { textToBase64 } from '@/utils/base64';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const width = ref(600);
|
||||
@@ -75,7 +76,7 @@ const svgString = computed(() => {
|
||||
</svg>
|
||||
`.trim();
|
||||
});
|
||||
const base64 = computed(() => 'data:image/svg+xml;base64,' + window.btoa(svgString.value));
|
||||
const base64 = computed(() => 'data:image/svg+xml;base64,' + textToBase64(svgString.value));
|
||||
|
||||
const { copy: copySVG } = useCopy({ source: svgString });
|
||||
const { copy: copyBase64 } = useCopy({ source: base64 });
|
||||
|
||||
@@ -60,29 +60,18 @@
|
||||
<script setup lang="ts">
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { isNotThrowing } from '@/utils/boolean';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const encodeInput = ref('Hello world :)');
|
||||
const encodeOutput = computed(() => {
|
||||
try {
|
||||
return encodeURIComponent(encodeInput.value);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const encodeOutput = computed(() => withDefaultOnError(() => encodeURIComponent(encodeInput.value), ''));
|
||||
|
||||
const encodedValidation = useValidation({
|
||||
source: encodeInput,
|
||||
rules: [
|
||||
{
|
||||
validator: (value) => {
|
||||
try {
|
||||
encodeURIComponent(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
validator: (value) => isNotThrowing(() => encodeURIComponent(value)),
|
||||
message: 'Impossible to parse this string',
|
||||
},
|
||||
],
|
||||
@@ -91,27 +80,13 @@ const encodedValidation = useValidation({
|
||||
const { copy: copyEncoded } = useCopy({ source: encodeOutput, text: 'Encoded string copied to the clipboard' });
|
||||
|
||||
const decodeInput = ref('Hello%20world%20%3A)');
|
||||
|
||||
const decodeOutput = computed(() => {
|
||||
try {
|
||||
return decodeURIComponent(decodeInput.value);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
const decodeOutput = computed(() => withDefaultOnError(() => decodeURIComponent(decodeInput.value), ''));
|
||||
|
||||
const decodeValidation = useValidation({
|
||||
source: encodeInput,
|
||||
rules: [
|
||||
{
|
||||
validator: (value) => {
|
||||
try {
|
||||
decodeURIComponent(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
validator: (value) => isNotThrowing(() => decodeURIComponent(value)),
|
||||
message: 'Impossible to parse this string',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -27,31 +27,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { SubdirectoryArrowRightRound } from '@vicons/material';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { isNotThrowing } from '@/utils/boolean';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { SubdirectoryArrowRightRound } from '@vicons/material';
|
||||
import { computed, ref } from 'vue';
|
||||
import InputCopyable from '../../components/InputCopyable.vue';
|
||||
|
||||
const urlToParse = ref('https://me:pwd@it-tools.tech:3000/url-parser?key1=value&key2=value2#the-hash');
|
||||
const urlParsed = computed<URL | undefined>(() => {
|
||||
try {
|
||||
return new URL(urlToParse.value);
|
||||
} catch (_) {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
const urlParsed = computed(() => withDefaultOnError(() => new URL(urlToParse.value), undefined));
|
||||
const validation = useValidation({
|
||||
source: urlToParse,
|
||||
rules: [
|
||||
{
|
||||
validator: (value) => {
|
||||
try {
|
||||
new URL(value);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
validator: (value) => isNotThrowing(() => new URL(value)),
|
||||
message: 'Invalid url',
|
||||
},
|
||||
],
|
||||
|
||||
69
src/utils/base64.test.ts
Normal file
69
src/utils/base64.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { base64ToText, isValidBase64, removePotentialDataAndMimePrefix, textToBase64 } from './base64';
|
||||
|
||||
describe('base64 utils', () => {
|
||||
describe('textToBase64', () => {
|
||||
it('should convert string into base64', () => {
|
||||
expect(textToBase64('')).to.eql('');
|
||||
expect(textToBase64('a')).to.eql('YQ==');
|
||||
expect(textToBase64('lorem ipsum')).to.eql('bG9yZW0gaXBzdW0=');
|
||||
expect(textToBase64('-1')).to.eql('LTE=');
|
||||
});
|
||||
});
|
||||
|
||||
describe('base64ToText', () => {
|
||||
it('should convert base64 into text', () => {
|
||||
expect(base64ToText('')).to.eql('');
|
||||
expect(base64ToText('YQ==')).to.eql('a');
|
||||
expect(base64ToText('bG9yZW0gaXBzdW0=')).to.eql('lorem ipsum');
|
||||
expect(base64ToText('data:text/plain;base64,bG9yZW0gaXBzdW0=')).to.eql('lorem ipsum');
|
||||
expect(base64ToText('LTE=')).to.eql('-1');
|
||||
});
|
||||
|
||||
it('should throw for incorrect base64 string', () => {
|
||||
expect(() => base64ToText('a')).to.throw('Incorrect base64 string');
|
||||
expect(() => base64ToText(' ')).to.throw('Incorrect base64 string');
|
||||
expect(() => base64ToText('é')).to.throw('Incorrect base64 string');
|
||||
// missing final '='
|
||||
expect(() => base64ToText('bG9yZW0gaXBzdW0')).to.throw('Incorrect base64 string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValidBase64', () => {
|
||||
it('should return true for correct base64 string', () => {
|
||||
expect(isValidBase64('')).to.eql(true);
|
||||
expect(isValidBase64('bG9yZW0gaXBzdW0=')).to.eql(true);
|
||||
expect(isValidBase64('LTE=')).to.eql(true);
|
||||
expect(isValidBase64('YQ==')).to.eql(true);
|
||||
expect(isValidBase64('data:text/plain;base64,YQ==')).to.eql(true);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePotentialDataAndMimePrefix', () => {
|
||||
it('should remove data prefix of string', () => {
|
||||
expect(removePotentialDataAndMimePrefix('')).to.eql('');
|
||||
expect(removePotentialDataAndMimePrefix('lorem ipsum')).to.eql('lorem ipsum');
|
||||
expect(removePotentialDataAndMimePrefix('bG9yZW0gaXBzdW0=')).to.eql('bG9yZW0gaXBzdW0=');
|
||||
expect(removePotentialDataAndMimePrefix('data:image/jpeg;base64,lorem')).to.eql('lorem');
|
||||
expect(removePotentialDataAndMimePrefix('data:image/jpeg;notbase64,lorem')).to.eql(
|
||||
'data:image/jpeg;notbase64,lorem',
|
||||
);
|
||||
expect(removePotentialDataAndMimePrefix('data:unknownmime;base64,lorem')).to.eql('lorem');
|
||||
});
|
||||
});
|
||||
});
|
||||
33
src/utils/base64.ts
Normal file
33
src/utils/base64.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export { textToBase64, base64ToText, isValidBase64, removePotentialDataAndMimePrefix };
|
||||
|
||||
function textToBase64(str: string) {
|
||||
return window.btoa(str);
|
||||
}
|
||||
|
||||
function base64ToText(str: string) {
|
||||
if (!isValidBase64(str)) {
|
||||
throw new Error('Incorrect base64 string');
|
||||
}
|
||||
|
||||
const cleanStr = removePotentialDataAndMimePrefix(str);
|
||||
|
||||
try {
|
||||
return window.atob(cleanStr);
|
||||
} catch (_) {
|
||||
throw new Error('Incorrect base64 string');
|
||||
}
|
||||
}
|
||||
|
||||
function removePotentialDataAndMimePrefix(str: string) {
|
||||
return str.replace(/^data:.*?;base64,/, '');
|
||||
}
|
||||
|
||||
function isValidBase64(str: string) {
|
||||
const cleanStr = removePotentialDataAndMimePrefix(str);
|
||||
|
||||
try {
|
||||
return window.btoa(window.atob(cleanStr)) === cleanStr;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
15
src/utils/boolean.test.ts
Normal file
15
src/utils/boolean.test.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { isNotThrowing } from './boolean';
|
||||
|
||||
describe('boolean utils', () => {
|
||||
describe('isNotThrowing', () => {
|
||||
it('should return if the call throws or false otherwise', () => {
|
||||
expect(isNotThrowing(() => {})).to.eql(true);
|
||||
expect(
|
||||
isNotThrowing(() => {
|
||||
throw new Error();
|
||||
}),
|
||||
).to.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
10
src/utils/boolean.ts
Normal file
10
src/utils/boolean.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export { isNotThrowing };
|
||||
|
||||
function isNotThrowing(cb: () => unknown): boolean {
|
||||
try {
|
||||
cb();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
16
src/utils/defaults.test.ts
Normal file
16
src/utils/defaults.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { withDefaultOnError } from './defaults';
|
||||
|
||||
describe('defaults util', () => {
|
||||
describe('withDefaultOnError', () => {
|
||||
it('should return the callback or the default one if the callback throws', () => {
|
||||
expect(withDefaultOnError(() => 'original', 'default')).to.eql('original');
|
||||
});
|
||||
|
||||
expect(
|
||||
withDefaultOnError(() => {
|
||||
throw '';
|
||||
}, 'default'),
|
||||
).to.eql('default');
|
||||
});
|
||||
});
|
||||
9
src/utils/defaults.ts
Normal file
9
src/utils/defaults.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { withDefaultOnError };
|
||||
|
||||
function withDefaultOnError<A, B>(cb: () => A, defaultValue: B): A | B {
|
||||
try {
|
||||
return cb();
|
||||
} catch (_) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user