mirror of
https://github.com/CorentinTh/it-tools.git
synced 2024-09-11 13:37:53 +03:00
Compare commits
5 Commits
ac5879a970
...
c2972ed6e1
Author | SHA1 | Date | |
---|---|---|---|
|
c2972ed6e1 | ||
|
b430baef40 | ||
|
dd4b7e687b | ||
|
30144aa3f5 | ||
|
c7c6d1e4ad |
12
components.d.ts
vendored
12
components.d.ts
vendored
@ -65,6 +65,7 @@ declare module '@vue/runtime-core' {
|
||||
CTooltip: typeof import('./src/ui/c-tooltip/c-tooltip.vue')['default']
|
||||
'CTooltip.demo': typeof import('./src/ui/c-tooltip/c-tooltip.demo.vue')['default']
|
||||
DateTimeConverter: typeof import('./src/tools/date-time-converter/date-time-converter.vue')['default']
|
||||
DaysCalculator: typeof import('./src/tools/days-calculator/days-calculator.vue')['default']
|
||||
'DemoHome.page': typeof import('./src/ui/demo/demo-home.page.vue')['default']
|
||||
DemoWrapper: typeof import('./src/ui/demo/demo-wrapper.vue')['default']
|
||||
DeviceInformation: typeof import('./src/tools/device-information/device-information.vue')['default']
|
||||
@ -89,6 +90,7 @@ 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']
|
||||
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
|
||||
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
|
||||
@ -126,25 +128,35 @@ 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']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
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']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
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']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSlider: typeof import('naive-ui')['NSlider']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
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']
|
||||
|
@ -92,6 +92,11 @@ tools:
|
||||
button:
|
||||
copy: Copy
|
||||
refresh: Refresh
|
||||
|
||||
days-calculator:
|
||||
title: Days calculator
|
||||
description: Find the number of days between two dates.
|
||||
|
||||
percentage-calculator:
|
||||
title: Percentage calculator
|
||||
description: Easily calculate percentages from a value to another value, or from a percentage to a value.
|
||||
|
@ -91,6 +91,11 @@ tools:
|
||||
button:
|
||||
copy: 复制
|
||||
refresh: 刷新
|
||||
|
||||
days-calculator:
|
||||
title: 天数计算器
|
||||
description: 计算两个日期之间的天数。
|
||||
|
||||
percentage-calculator:
|
||||
title: 百分比计算器
|
||||
description: 轻松计算从一个值到另一个值的百分比,或从百分比到值的百分比。
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -48,7 +48,7 @@ const output = computed(() => transformer.value(input.value));
|
||||
monospace
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div overflow-auto>
|
||||
<div mb-5px>
|
||||
{{ outputLabel }}
|
||||
</div>
|
||||
|
@ -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<string>; 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<string>; 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<string>; filename?: Ref<string>; extension?: Ref<string> }) {
|
||||
return {
|
||||
download() {
|
||||
downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function previewImageFromBase64(base64String: string): HTMLImageElement {
|
||||
if (base64String === '') {
|
||||
throw new Error('Base64 string is empty');
|
||||
}
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = base64String;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.appendChild(img);
|
||||
|
||||
const previewContainer = document.getElementById('previewContainer');
|
||||
if (previewContainer) {
|
||||
previewContainer.innerHTML = '';
|
||||
previewContainer.appendChild(container);
|
||||
}
|
||||
else {
|
||||
throw new Error('Preview container element not found');
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
<template>
|
||||
<c-card title="Base64 to file">
|
||||
<n-grid cols="3" x-gap="12">
|
||||
<n-gi span="2">
|
||||
<c-input-text
|
||||
v-model:value="fileName"
|
||||
label="File Name"
|
||||
placeholder="Download filename"
|
||||
mb-2
|
||||
/>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<c-input-text
|
||||
v-model:value="fileExtension"
|
||||
label="Extension"
|
||||
placeholder="Extension"
|
||||
mb-2
|
||||
/>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<c-input-text
|
||||
v-model:value="base64Input"
|
||||
multiline
|
||||
@ -53,7 +107,14 @@ async function onUpload(file: File) {
|
||||
mb-2
|
||||
/>
|
||||
|
||||
<div flex justify-center>
|
||||
<div flex justify-center py-2>
|
||||
<div id="previewContainer" />
|
||||
</div>
|
||||
|
||||
<div flex justify-center gap-3>
|
||||
<c-button :disabled="base64Input === '' || !base64InputValidation.isValid" @click="previewImage()">
|
||||
Preview image
|
||||
</c-button>
|
||||
<c-button :disabled="base64Input === '' || !base64InputValidation.isValid" @click="downloadFile()">
|
||||
Download file
|
||||
</c-button>
|
||||
|
16
src/tools/days-calculator/days-calculator.service.ts
Normal file
16
src/tools/days-calculator/days-calculator.service.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { formatDuration } from 'date-fns';
|
||||
|
||||
export function formatMsDuration(duration: number) {
|
||||
const ms = Math.floor(duration % 1000);
|
||||
const secs = Math.floor(((duration - ms) / 1000) % 60);
|
||||
const mins = Math.floor((((duration - ms) / 1000 - secs) / 60) % 60);
|
||||
const hrs = Math.floor((((duration - ms) / 1000 - secs) / 60 - mins) / 60);
|
||||
|
||||
return (
|
||||
formatDuration({
|
||||
hours: hrs,
|
||||
minutes: mins,
|
||||
seconds: secs,
|
||||
}) + (ms > 0 ? ` ${ms} ms` : '')
|
||||
);
|
||||
}
|
37
src/tools/days-calculator/days-calculator.vue
Normal file
37
src/tools/days-calculator/days-calculator.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
const startDate = ref<Date | null>(null);
|
||||
const endDate = ref<Date | null>(null);
|
||||
const includeEndDate = ref(false);
|
||||
|
||||
const calculatedDays = computed<string | null>(() => {
|
||||
if (startDate.value && endDate.value) {
|
||||
const start = new Date(startDate.value);
|
||||
const end = new Date(endDate.value);
|
||||
if (!Number.isNaN(start.getTime()) && !Number.isNaN(end.getTime())) {
|
||||
const diff = Math.abs(end.getTime() - start.getTime());
|
||||
const days = Math.ceil(diff / (1000 * 60 * 60 * 24));
|
||||
return (includeEndDate.value ? days + 1 : days).toString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<n-form>
|
||||
<div w-full flex gap-4>
|
||||
<n-form-item label="Start Date">
|
||||
<n-date-picker v-model:value="startDate" type="date" />
|
||||
</n-form-item>
|
||||
<n-form-item label="End Date">
|
||||
<n-date-picker v-model:value="endDate" type="date" />
|
||||
</n-form-item>
|
||||
</div>
|
||||
<n-form-item label="Include end date in calculation (1 day is added)" label-placement="left">
|
||||
<n-checkbox v-model:checked="includeEndDate" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<c-input-text label="Calculated days" :value="calculatedDays" readonly />
|
||||
</div>
|
||||
</template>
|
12
src/tools/days-calculator/index.ts
Normal file
12
src/tools/days-calculator/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { CalendarPlus } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
import { translate } from '@/plugins/i18n.plugin';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: translate('tools.days-calculator.title'),
|
||||
path: '/days-calculator',
|
||||
description: translate('tools.days-calculator.description'),
|
||||
keywords: ['days', 'calculator', 'between', 'two', 'dates'],
|
||||
component: () => import('./days-calculator.vue'),
|
||||
icon: CalendarPlus,
|
||||
});
|
@ -52,6 +52,7 @@ import { tool as dateTimeConverter } from './date-time-converter';
|
||||
import { tool as deviceInformation } from './device-information';
|
||||
import { tool as cypher } from './encryption';
|
||||
import { tool as etaCalculator } from './eta-calculator';
|
||||
import { tool as daysCalculator } from './days-calculator';
|
||||
import { tool as percentageCalculator } from './percentage-calculator';
|
||||
import { tool as gitMemo } from './git-memo';
|
||||
import { tool as hashText } from './hash-text';
|
||||
@ -156,7 +157,7 @@ export const toolsByCategory: ToolCategory[] = [
|
||||
},
|
||||
{
|
||||
name: 'Math',
|
||||
components: [mathEvaluator, etaCalculator, percentageCalculator],
|
||||
components: [mathEvaluator, etaCalculator, percentageCalculator, daysCalculator],
|
||||
},
|
||||
{
|
||||
name: 'Measurement',
|
||||
|
@ -39,7 +39,7 @@ const validation = useValidation({
|
||||
{{ section.title }}
|
||||
</th>
|
||||
<tr v-for="{ claim, claimDescription, friendlyValue, value } in decodedJWT[section.key]" :key="claim + value">
|
||||
<td class="claims">
|
||||
<td class="claims" style="vertical-align: top;">
|
||||
<span font-bold>
|
||||
{{ claim }}
|
||||
</span>
|
||||
@ -47,7 +47,7 @@ const validation = useValidation({
|
||||
({{ claimDescription }})
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td style="word-wrap: break-word;word-break: break-all;">
|
||||
<span>{{ value }}</span>
|
||||
<span v-if="friendlyValue" ml-2 op-70>
|
||||
({{ friendlyValue }})
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user