mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-07 14:57:12 -04:00
Merge 97e8ff6c5c
into 07eea0f484
This commit is contained in:
commit
559e6dc423
15 changed files with 6936 additions and 8553 deletions
|
@ -286,6 +286,9 @@
|
|||
"watchTriggerable": true,
|
||||
"watchWithFilter": true,
|
||||
"whenever": true,
|
||||
"toValue": true
|
||||
"toValue": true,
|
||||
"injectLocal": true,
|
||||
"provideLocal": true,
|
||||
"useClipboardItems": true
|
||||
}
|
||||
}
|
||||
|
|
9
auto-imports.d.ts
vendored
9
auto-imports.d.ts
vendored
|
@ -36,6 +36,7 @@ declare global {
|
|||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
|
@ -65,6 +66,7 @@ declare global {
|
|||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
|
@ -128,6 +130,7 @@ declare global {
|
|||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
|
@ -326,6 +329,7 @@ declare module 'vue' {
|
|||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
|
@ -355,6 +359,7 @@ declare module 'vue' {
|
|||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
|
@ -418,6 +423,7 @@ declare module 'vue' {
|
|||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||
|
@ -610,6 +616,7 @@ declare module '@vue/runtime-core' {
|
|||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
|
@ -639,6 +646,7 @@ declare module '@vue/runtime-core' {
|
|||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
|
@ -702,6 +710,7 @@ declare module '@vue/runtime-core' {
|
|||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||
|
|
1
components.d.ts
vendored
1
components.d.ts
vendored
|
@ -135,6 +135,7 @@ declare module '@vue/runtime-core' {
|
|||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NH1: typeof import('naive-ui')['NH1']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
|
|
15049
pnpm-lock.yaml
generated
15049
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -128,7 +128,7 @@ function activateOption(option: PaletteOption) {
|
|||
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
||||
|
||||
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
||||
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
|
||||
<div ml-3 mt-3 text-sm text-primary font-bold op-60>
|
||||
{{ category }}
|
||||
</div>
|
||||
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test.describe('Tool - Text to ASCII binary', () => {
|
||||
test.describe('Tool - Text to UTF-8 binary', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/text-to-binary');
|
||||
});
|
||||
|
||||
test('Has correct title', async ({ page }) => {
|
||||
await expect(page).toHaveTitle('Text to ASCII binary - IT Tools');
|
||||
await expect(page).toHaveTitle('Text to UTF-8 binary - IT Tools');
|
||||
});
|
||||
|
||||
test('Text to binary conversion', async ({ page }) => {
|
||||
|
@ -17,7 +17,9 @@ test.describe('Tool - Text to ASCII binary', () => {
|
|||
});
|
||||
|
||||
test('Binary to text conversion', async ({ page }) => {
|
||||
await page.getByTestId('binary-to-text-input').fill('01101001 01110100 00101101 01110100 01101111 01101111 01101100 01110011');
|
||||
await page
|
||||
.getByTestId('binary-to-text-input')
|
||||
.fill('01101001 01110100 00101101 01110100 01101111 01101111 01101100 01110011');
|
||||
const text = await page.getByTestId('binary-to-text-output').inputValue();
|
||||
|
||||
expect(text).toEqual('it-tools');
|
||||
|
|
|
@ -1,32 +1,103 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { convertAsciiBinaryToText, convertTextToAsciiBinary } from './text-to-binary.models';
|
||||
import { convertTextToUtf8Binary, convertUtf8BinaryToText } from './text-to-binary.models';
|
||||
|
||||
describe('text-to-binary', () => {
|
||||
describe('convertTextToAsciiBinary', () => {
|
||||
it('a text string is converted to its ascii binary representation', () => {
|
||||
expect(convertTextToAsciiBinary('A')).toBe('01000001');
|
||||
expect(convertTextToAsciiBinary('hello')).toBe('01101000 01100101 01101100 01101100 01101111');
|
||||
expect(convertTextToAsciiBinary('')).toBe('');
|
||||
const utf8Tests = [
|
||||
{
|
||||
text: '文字',
|
||||
binary: '11100110 10010110 10000111 11100101 10101101 10010111',
|
||||
decimal: '',
|
||||
octal: '',
|
||||
hex: '',
|
||||
},
|
||||
{
|
||||
text: '💩',
|
||||
binary: '11110000 10011111 10010010 10101001',
|
||||
decimal: '',
|
||||
octal: '',
|
||||
hex: '',
|
||||
},
|
||||
];
|
||||
|
||||
describe('convertTextToUtf8Binary', () => {
|
||||
it('a text string is converted to its UTF-8 binary representation', () => {
|
||||
expect(convertTextToUtf8Binary('A')).toBe('01000001');
|
||||
expect(convertTextToUtf8Binary('A', { base: 8 })).toBe('0101');
|
||||
expect(convertTextToUtf8Binary('A', { base: 10 })).toBe('65');
|
||||
expect(convertTextToUtf8Binary('A', { base: 16 })).toBe('41');
|
||||
expect(convertTextToUtf8Binary('hello')).toBe('01101000 01100101 01101100 01101100 01101111');
|
||||
expect(convertTextToUtf8Binary('hello', { base: 8 })).toBe('0150 0145 0154 0154 0157');
|
||||
expect(convertTextToUtf8Binary('hello', { base: 10 })).toBe('104 101 108 108 111');
|
||||
expect(convertTextToUtf8Binary('hello', { base: 16 })).toBe('68 65 6c 6c 6f');
|
||||
expect(convertTextToUtf8Binary('')).toBe('');
|
||||
});
|
||||
it('the separator between octets can be changed', () => {
|
||||
expect(convertTextToAsciiBinary('hello', { separator: '' })).toBe('0110100001100101011011000110110001101111');
|
||||
expect(convertTextToUtf8Binary('hello', { separator: '' })).toBe('0110100001100101011011000110110001101111');
|
||||
expect(convertTextToUtf8Binary('hello', { separator: '-' })).toBe('01101000-01100101-01101100-01101100-01101111');
|
||||
expect(convertTextToUtf8Binary('hello', { separator: '-', base: 16 })).toBe('68-65-6c-6c-6f');
|
||||
});
|
||||
it('works with non-ASCII input', () => {
|
||||
for (const { text, binary } of utf8Tests) {
|
||||
const converted = convertTextToUtf8Binary(text);
|
||||
expect(converted).toBe(binary);
|
||||
}
|
||||
|
||||
expect(convertTextToUtf8Binary('💩 A', { base: 2 })).toBe('11110000 10011111 10010010 10101001 00100000 01000001');
|
||||
expect(convertTextToUtf8Binary('💩 A', { base: 8 })).toBe('0360 0237 0222 0251 040 0101');
|
||||
expect(convertTextToUtf8Binary('💩 A', { base: 10 })).toBe('240 159 146 169 32 65');
|
||||
expect(convertTextToUtf8Binary('💩 A', { base: 16 })).toBe('f0 9f 92 a9 20 41');
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertAsciiBinaryToText', () => {
|
||||
describe('convertUtf8BinaryToText', () => {
|
||||
it('an ascii binary string is converted to its text representation', () => {
|
||||
expect(convertAsciiBinaryToText('01101000 01100101 01101100 01101100 01101111')).toBe('hello');
|
||||
expect(convertAsciiBinaryToText('01000001')).toBe('A');
|
||||
expect(convertTextToAsciiBinary('')).toBe('');
|
||||
expect(convertUtf8BinaryToText('01101000 01100101 01101100 01101100 01101111')).toBe('hello');
|
||||
expect(convertUtf8BinaryToText('01101000 01100101 01101100 01101100 01101111', { base: 2 })).toBe('hello');
|
||||
expect(convertUtf8BinaryToText('0150 0145 0154 0154 0157', { base: 8 })).toBe('hello');
|
||||
expect(convertUtf8BinaryToText('104 101 108 108 111', { base: 10 })).toBe('hello');
|
||||
expect(convertUtf8BinaryToText('68 65 6c 6c 6f', { base: 16 })).toBe('hello');
|
||||
|
||||
expect(convertUtf8BinaryToText('11110000 10011111 10010010 10101001 00100000 01000001', { base: 2 })).toBe('💩 A');
|
||||
expect(convertUtf8BinaryToText('0360 0237 0222 0251 040 0101', { base: 8 })).toBe('💩 A');
|
||||
expect(convertUtf8BinaryToText('240 159 146 169 32 65', { base: 10 })).toBe('💩 A');
|
||||
expect(convertUtf8BinaryToText('f0 9f 92 a9 20 41', { base: 16 })).toBe('💩 A');
|
||||
|
||||
expect(convertUtf8BinaryToText('')).toBe('');
|
||||
|
||||
expect(convertUtf8BinaryToText('01000001')).toBe('A');
|
||||
expect(convertUtf8BinaryToText('0101', { base: 8 })).toBe('A');
|
||||
expect(convertUtf8BinaryToText('65', { base: 10 })).toBe('A');
|
||||
expect(convertUtf8BinaryToText('41', { base: 16 })).toBe('A');
|
||||
});
|
||||
|
||||
it('the given binary string is cleaned before conversion', () => {
|
||||
expect(convertAsciiBinaryToText(' 01000 001garbage')).toBe('A');
|
||||
it('the given string is cleaned before conversion', () => {
|
||||
expect(convertUtf8BinaryToText(' 01000 001garbage')).toBe('A');
|
||||
expect(convertUtf8BinaryToText(' 65garbage', { base: 10 })).toBe('A');
|
||||
expect(convertUtf8BinaryToText(' 41xxxx', { base: 16 })).toBe('A');
|
||||
});
|
||||
|
||||
it('throws an error if the given binary string as no complete octet', () => {
|
||||
expect(() => convertAsciiBinaryToText('010000011')).toThrow('Invalid binary string');
|
||||
expect(() => convertAsciiBinaryToText('1')).toThrow('Invalid binary string');
|
||||
it('throws an error if the given binary string is not an integer number of complete octets', () => {
|
||||
expect(() => convertUtf8BinaryToText('010000011')).toThrow('Invalid binary string');
|
||||
expect(() => convertUtf8BinaryToText('010000011 010000011')).toThrow('Invalid binary string');
|
||||
expect(() => convertUtf8BinaryToText('1')).toThrow('Invalid binary string');
|
||||
});
|
||||
|
||||
it('throws an error if the given binary string is not valid UTF-8', () => {
|
||||
expect(() => convertUtf8BinaryToText('11111111')).toThrow();
|
||||
});
|
||||
|
||||
it('the given string is cleaned from prefix before conversion', () => {
|
||||
expect(convertUtf8BinaryToText('0b01000001')).toBe('A');
|
||||
expect(convertUtf8BinaryToText('0x41', { base: 16 })).toBe('A');
|
||||
expect(convertUtf8BinaryToText('0x68 0x65 0x6c 0x6c 0x6f', { base: 16 })).toBe('hello');
|
||||
expect(convertUtf8BinaryToText('\\x68\\x65\\x6c\\x6c\\x6f', { base: 16 })).toBe('hello');
|
||||
});
|
||||
|
||||
it('works with non-ASCII input', () => {
|
||||
for (const { text, binary } of utf8Tests) {
|
||||
const reverted = convertUtf8BinaryToText(binary);
|
||||
expect(reverted).toBe(text);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,22 +1,67 @@
|
|||
export { convertTextToAsciiBinary, convertAsciiBinaryToText };
|
||||
export { convertTextToUtf8Binary, convertUtf8BinaryToText };
|
||||
|
||||
function convertTextToAsciiBinary(text: string, { separator = ' ' }: { separator?: string } = {}): string {
|
||||
return text
|
||||
.split('')
|
||||
.map(char => char.charCodeAt(0).toString(2).padStart(8, '0'))
|
||||
.join(separator);
|
||||
}
|
||||
export type EncodingBase = 2 | 8 | 10 | 16;
|
||||
|
||||
function convertAsciiBinaryToText(binary: string): string {
|
||||
const cleanBinary = binary.replace(/[^01]/g, '');
|
||||
|
||||
if (cleanBinary.length % 8) {
|
||||
throw new Error('Invalid binary string');
|
||||
function convertTextToUtf8Binary(text: string, { separator = ' ', base = 2 }: { separator?: string; base?: EncodingBase } = {}): string {
|
||||
if (!text?.trim()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return cleanBinary
|
||||
.split(/(\d{8})/)
|
||||
.filter(Boolean)
|
||||
.map(binary => String.fromCharCode(Number.parseInt(binary, 2)))
|
||||
.join('');
|
||||
return [...new TextEncoder().encode(text)].map((char) => {
|
||||
const charInBase = char.toString(base);
|
||||
if (base === 2) {
|
||||
return charInBase.padStart(8, '0');
|
||||
}
|
||||
if (base === 8) {
|
||||
return `0${charInBase}`;
|
||||
}
|
||||
if (base === 16) {
|
||||
return charInBase.padStart(2, '0');
|
||||
}
|
||||
|
||||
return charInBase;
|
||||
}).join(separator);
|
||||
}
|
||||
|
||||
function convertUtf8BinaryToText(binary: string, { base = 2 }: { base?: EncodingBase } = {}): string {
|
||||
if (!binary?.trim()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let codepoints: number[] = [];
|
||||
if (base === 2) {
|
||||
const cleanBinary = binary.replace(/0b/g, '').replace(/[^01]/g, '').trim();
|
||||
|
||||
if (cleanBinary.length % 8) {
|
||||
throw new Error('Invalid binary string');
|
||||
}
|
||||
|
||||
codepoints = cleanBinary
|
||||
.split(/([01]{8})/)
|
||||
.filter(Boolean)
|
||||
.map(binary => Number.parseInt(binary, 2));
|
||||
}
|
||||
else if (base === 16) {
|
||||
const cleanBinary = binary.replace(/0x|\\x/g, '').replace(/[^0-9A-Fa-f]/g, '');
|
||||
|
||||
if (cleanBinary.length % 2) {
|
||||
throw new Error('Invalid hexadecimal string');
|
||||
}
|
||||
|
||||
codepoints = cleanBinary
|
||||
.split(/([0-9A-Fa-f]{2})/)
|
||||
.filter(Boolean)
|
||||
.map(binary => Number.parseInt(binary, 16));
|
||||
}
|
||||
else {
|
||||
const cleanBinary = binary.replace(/0o/g, '').replace(/[^\d\s]/g, '');
|
||||
codepoints = cleanBinary
|
||||
.split(/\s/)
|
||||
.filter(Boolean)
|
||||
.map(binary => Number.parseInt(binary, base));
|
||||
}
|
||||
|
||||
return new TextDecoder(undefined, { fatal: true }).decode(
|
||||
Uint8Array.from(codepoints),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,42 +1,95 @@
|
|||
<script setup lang="ts">
|
||||
import { convertAsciiBinaryToText, convertTextToAsciiBinary } from './text-to-binary.models';
|
||||
import { type EncodingBase, convertTextToUtf8Binary, convertUtf8BinaryToText } from './text-to-binary.models';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
import { isNotThrowing } from '@/utils/boolean';
|
||||
import { useQueryParamOrStorage } from '@/composable/queryParams';
|
||||
|
||||
const base = useQueryParamOrStorage({ name: 'base', storageName: 'txt-bin:base', defaultValue: '2' });
|
||||
const inputText = ref('');
|
||||
const binaryFromText = computed(() => convertTextToAsciiBinary(inputText.value));
|
||||
const binaryFromText = computed(() => convertTextToUtf8Binary(inputText.value, { base: Number(base.value) as EncodingBase }));
|
||||
const { copy: copyBinary } = useCopy({ source: binaryFromText });
|
||||
|
||||
const inputBinary = ref('');
|
||||
const textFromBinary = computed(() => withDefaultOnError(() => convertAsciiBinaryToText(inputBinary.value), ''));
|
||||
const textFromBinary = computed(() => withDefaultOnError(() => convertUtf8BinaryToText(inputBinary.value, { base: Number(base.value) as EncodingBase }), ''));
|
||||
const inputBinaryValidationRules = [
|
||||
{
|
||||
validator: (value: string) => isNotThrowing(() => convertAsciiBinaryToText(value)),
|
||||
message: 'Binary should be a valid ASCII binary string with multiples of 8 bits',
|
||||
validator: (value: string) => isNotThrowing(() => convertUtf8BinaryToText(value)),
|
||||
message: 'Binary should be a valid UTF-8 binary string with multiples of 8 bits',
|
||||
},
|
||||
];
|
||||
const { copy: copyText } = useCopy({ source: textFromBinary });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<c-card title="Text to ASCII binary">
|
||||
<c-input-text v-model:value="inputText" multiline placeholder="e.g. 'Hello world'" label="Enter text to convert to binary" autosize autofocus raw-text test-id="text-to-binary-input" />
|
||||
<c-input-text v-model:value="binaryFromText" label="Binary from your text" multiline raw-text readonly mt-2 placeholder="The binary representation of your text will be here" test-id="text-to-binary-output" />
|
||||
<div mt-2 flex justify-center>
|
||||
<c-button :disabled="!binaryFromText" @click="copyBinary()">
|
||||
Copy binary to clipboard
|
||||
</c-button>
|
||||
</div>
|
||||
</c-card>
|
||||
<div>
|
||||
<c-select
|
||||
v-model:value="base"
|
||||
label="Conversion Base:"
|
||||
label-position="left"
|
||||
mb-2
|
||||
:options="[
|
||||
{ value: '2', label: 'Binary' },
|
||||
{ value: '8', label: 'Octal' },
|
||||
{ value: '10', label: 'Decimal' },
|
||||
{ value: '16', label: 'Hexadecimal' },
|
||||
]"
|
||||
/>
|
||||
|
||||
<c-card title="ASCII binary to text">
|
||||
<c-input-text v-model:value="inputBinary" multiline placeholder="e.g. '01001000 01100101 01101100 01101100 01101111'" label="Enter binary to convert to text" autosize raw-text :validation-rules="inputBinaryValidationRules" test-id="binary-to-text-input" />
|
||||
<c-input-text v-model:value="textFromBinary" label="Text from your binary" multiline raw-text readonly mt-2 placeholder="The text representation of your binary will be here" test-id="binary-to-text-output" />
|
||||
<div mt-2 flex justify-center>
|
||||
<c-button :disabled="!textFromBinary" @click="copyText()">
|
||||
Copy text to clipboard
|
||||
</c-button>
|
||||
</div>
|
||||
</c-card>
|
||||
<c-card title="Text to UTF-8 binary">
|
||||
<c-input-text
|
||||
v-model:value="inputText"
|
||||
multiline
|
||||
placeholder="e.g. 'Hello world'"
|
||||
label="Enter text to convert to binary"
|
||||
autosize
|
||||
autofocus
|
||||
raw-text
|
||||
test-id="text-to-binary-input"
|
||||
/>
|
||||
<c-input-text
|
||||
v-model:value="binaryFromText"
|
||||
label="Binary from your text"
|
||||
multiline
|
||||
raw-text
|
||||
readonly
|
||||
mt-2
|
||||
placeholder="The binary representation of your text will be here"
|
||||
test-id="text-to-binary-output"
|
||||
/>
|
||||
<div mt-2 flex justify-center>
|
||||
<c-button :disabled="!binaryFromText" @click="copyBinary()">
|
||||
Copy binary to clipboard
|
||||
</c-button>
|
||||
</div>
|
||||
</c-card>
|
||||
|
||||
<c-card title="UTF-8 binary to text">
|
||||
<c-input-text
|
||||
v-model:value="inputBinary"
|
||||
multiline
|
||||
placeholder="e.g. '01001000 01100101 01101100 01101100 01101111'"
|
||||
label="Enter binary to convert to text"
|
||||
autosize
|
||||
raw-text
|
||||
:validation-rules="inputBinaryValidationRules"
|
||||
test-id="binary-to-text-input"
|
||||
/>
|
||||
<c-input-text
|
||||
v-model:value="textFromBinary"
|
||||
label="Text from your binary"
|
||||
multiline
|
||||
raw-text
|
||||
readonly
|
||||
mt-2
|
||||
placeholder="The text representation of your binary will be here"
|
||||
test-id="binary-to-text-output"
|
||||
/>
|
||||
<div mt-2 flex justify-center>
|
||||
<c-button :disabled="!textFromBinary" @click="copyText()">
|
||||
Copy text to clipboard
|
||||
</c-button>
|
||||
</div>
|
||||
</c-card>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -10,16 +10,18 @@ test.describe('Tool - Text to Unicode', () => {
|
|||
});
|
||||
|
||||
test('Text to unicode conversion', async ({ page }) => {
|
||||
await page.getByTestId('text-to-unicode-input').fill('it-tools');
|
||||
await page.getByTestId('text-to-unicode-input').fill('"it-tools" 文字');
|
||||
const unicode = await page.getByTestId('text-to-unicode-output').inputValue();
|
||||
|
||||
expect(unicode).toEqual('it-tools');
|
||||
// eslint-disable-next-line unicorn/escape-case
|
||||
expect(unicode).toEqual(String.raw`\u0022it-tools\u0022 \u6587\u5b57`);
|
||||
});
|
||||
|
||||
test('Unicode to text conversion', async ({ page }) => {
|
||||
await page.getByTestId('unicode-to-text-input').fill('it-tools');
|
||||
// eslint-disable-next-line unicorn/escape-case
|
||||
await page.getByTestId('unicode-to-text-input').fill(String.raw`\u0022it-tools\u0022 \u6587\u5b57`);
|
||||
const text = await page.getByTestId('unicode-to-text-output').inputValue();
|
||||
|
||||
expect(text).toEqual('it-tools');
|
||||
expect(text).toEqual('"it-tools" 文字');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,18 @@ describe('text-to-unicode', () => {
|
|||
describe('convertTextToUnicode', () => {
|
||||
it('a text string is converted to unicode representation', () => {
|
||||
expect(convertTextToUnicode('A')).toBe('A');
|
||||
expect(convertTextToUnicode('linke the string convert to unicode')).toBe('linke the string convert to unicode');
|
||||
expect(convertTextToUnicode('💩 AĀ')).toBe('💩 AĀ');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'antiuni' })).toBe('\\u1f4a9\\u20\\u41\\u0100');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'css' })).toBe('\\01f4a9\\000020\\000041\\000100');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'htmldec' })).toBe('💩 AĀ');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'htmlhex' })).toBe('💩 AĀ');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'uniplus' })).toBe('U+1f4a9 U+00020 U+00041 U+00100');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'python' })).toBe('\\U1f4a9\\x20\\x41\\u0100');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'js' })).toBe('\\u{1f4a9}\\u0020\\u0041\\u0100');
|
||||
expect(convertTextToUnicode('💩 AĀ', { encoding: 'utf16' })).toBe('\\ud83d\\udca9\\u0020\\u0041\\u0100');
|
||||
expect(convertTextToUnicode('💩 hello AĀ', { skipAscii: true })).toBe('💩 hello AĀ');
|
||||
expect(convertTextToUnicode('linke the string convert to unicode')).toBe(
|
||||
'linke the string convert to unicode');
|
||||
expect(convertTextToUnicode('')).toBe('');
|
||||
});
|
||||
});
|
||||
|
@ -13,6 +24,16 @@ describe('text-to-unicode', () => {
|
|||
describe('convertUnicodeToText', () => {
|
||||
it('an unicode string is converted to its text representation', () => {
|
||||
expect(convertUnicodeToText('A')).toBe('A');
|
||||
expect(convertUnicodeToText('\\u1f4a9\\u20\\u41\\u0100')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('\\01f4a9\\000020\\000041\\000100')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('💩 AĀ')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('💩 AĀ')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('U+1f4a9 U+00020 U+00041 U+00100')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('\\U1f4a9\\x20\\x41\\u0100')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('\\u{1f4a9}\\u0020\\u0041\\u0100')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('\\ud83d\\udca9\\u0020\\u0041\\u0100')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('\\01f4a9 AĀ')).toBe('💩 AĀ');
|
||||
expect(convertUnicodeToText('💩 hello AĀ')).toBe('💩 hello AĀ');
|
||||
expect(convertUnicodeToText('linke the string convert to unicode')).toBe('linke the string convert to unicode');
|
||||
expect(convertUnicodeToText('')).toBe('');
|
||||
});
|
||||
|
|
|
@ -1,9 +1,66 @@
|
|||
function convertTextToUnicode(text: string): string {
|
||||
return text.split('').map(value => `&#${value.charCodeAt(0)};`).join('');
|
||||
export type Encoding = 'htmldec' | 'htmlhex' | 'uniplus' | 'antiuni' | 'css' | 'python' | 'js' | 'utf16';
|
||||
|
||||
const ALL_PRINTABLE_ASCII = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~';
|
||||
|
||||
function convertTextToUnicode(text: string, { encoding = 'htmldec', skipAscii = false }: { encoding?: Encoding; skipAscii?: boolean } = {}): string {
|
||||
let prefix: (value: number) => string;
|
||||
let suffix: (value: number) => string = () => '';
|
||||
let base = 16;
|
||||
let padding: (value: number) => number = () => 0;
|
||||
let separator = '';
|
||||
let codepoints = [...text];
|
||||
if (encoding === 'htmldec') {
|
||||
prefix = () => '&#';
|
||||
base = 10;
|
||||
suffix = () => ';';
|
||||
}
|
||||
else if (encoding === 'htmlhex') {
|
||||
prefix = () => '&#x';
|
||||
suffix = () => ';';
|
||||
}
|
||||
else if (encoding === 'uniplus') {
|
||||
prefix = () => 'U+';
|
||||
padding = () => 5;
|
||||
separator = ' ';
|
||||
}
|
||||
else if (encoding === 'antiuni') {
|
||||
prefix = () => '\\u';
|
||||
padding = (value: number) => value < 256 ? 2 : 4;
|
||||
}
|
||||
else if (encoding === 'utf16') {
|
||||
prefix = () => '\\u';
|
||||
padding = () => 4;
|
||||
codepoints = text.split('');
|
||||
}
|
||||
else if (encoding === 'python') {
|
||||
prefix = (value: number) => value < 256 ? '\\x' : (value < 65536 ? '\\u' : '\\U');
|
||||
padding = (value: number) => value < 256 ? 2 : 4;
|
||||
}
|
||||
else if (encoding === 'js') {
|
||||
prefix = (value: number) => value < 65536 ? '\\u' : '\\u{';
|
||||
suffix = (value: number) => value < 65536 ? '' : '}';
|
||||
padding = () => 4;
|
||||
}
|
||||
else if (encoding === 'css') {
|
||||
prefix = () => '\\';
|
||||
padding = () => 6;
|
||||
}
|
||||
|
||||
return codepoints.map((value) => {
|
||||
if (skipAscii && ALL_PRINTABLE_ASCII.includes(value)) {
|
||||
return value;
|
||||
}
|
||||
const charCode = value.codePointAt(0) || 0xFF;
|
||||
return `${prefix(charCode)}${charCode.toString(base).padStart(padding(charCode), '0')}${suffix(charCode)}`;
|
||||
}).join(separator);
|
||||
}
|
||||
|
||||
function convertUnicodeToText(unicodeStr: string): string {
|
||||
return unicodeStr.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec));
|
||||
return unicodeStr
|
||||
.replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(dec))
|
||||
.replace(/&#[xX]([0-9A-Fa-f]+);/g, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
|
||||
.replace(/\\u\{([0-9A-Fa-f]+)\}/g, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
|
||||
.replace(/(?:\\[uUx]|\\|\s*U\+)([0-9A-Fa-f]+)/g, (match, hex) => String.fromCodePoint(Number.parseInt(hex, 16))); // NOSONAR
|
||||
}
|
||||
|
||||
export { convertTextToUnicode, convertUnicodeToText };
|
||||
|
|
|
@ -1,18 +1,46 @@
|
|||
<script setup lang="ts">
|
||||
import { convertTextToUnicode, convertUnicodeToText } from './text-to-unicode.service';
|
||||
import { type Encoding, convertTextToUnicode, convertUnicodeToText } from './text-to-unicode.service';
|
||||
import { useQueryParamOrStorage } from '@/composable/queryParams';
|
||||
import { useCopy } from '@/composable/copy';
|
||||
|
||||
const encoding = useQueryParamOrStorage({ name: 'enc', storageName: 'txt-uni:enc', defaultValue: 'htmldec' });
|
||||
const skipAscii = useQueryParamOrStorage({ name: 'skipAscii', storageName: 'txt-uni:asc', defaultValue: false });
|
||||
|
||||
const inputText = ref('');
|
||||
const unicodeFromText = computed(() => inputText.value.trim() === '' ? '' : convertTextToUnicode(inputText.value));
|
||||
const unicodeFromText = computed(() => inputText.value.trim() === ''
|
||||
? ''
|
||||
: convertTextToUnicode(inputText.value, { encoding: encoding.value as Encoding, skipAscii: skipAscii.value }));
|
||||
const { copy: copyUnicode } = useCopy({ source: unicodeFromText });
|
||||
|
||||
const inputUnicode = ref('');
|
||||
const textFromUnicode = computed(() => inputUnicode.value.trim() === '' ? '' : convertUnicodeToText(inputUnicode.value));
|
||||
const textFromUnicode = computed(() => inputUnicode.value.trim() === ''
|
||||
? ''
|
||||
: convertUnicodeToText(inputUnicode.value));
|
||||
const { copy: copyText } = useCopy({ source: textFromUnicode });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<c-card title="Text to Unicode">
|
||||
<c-select
|
||||
v-model:value="encoding"
|
||||
label="Unicode encoding"
|
||||
:options="[
|
||||
{ value: 'htmldec', label: 'HTML Decimal (&#160;)' },
|
||||
{ value: 'htmlhex', label: 'HTML Hexadecimal (&#xA0;)' },
|
||||
{ value: 'uniplus', label: 'U+00A0' },
|
||||
{ value: 'antiuni', label: '\\u00A0' },
|
||||
{ value: 'css', label: 'CSS (\\0000A0)' },
|
||||
{ value: 'python', label: 'Python (\\xA0, \\u00A0, \\U100A0)' },
|
||||
{ value: 'js', label: 'Python (\\u00A0; \\u{100A0})' },
|
||||
{ value: 'utf16', label: 'UTF 16 (with surrogates)' },
|
||||
]"
|
||||
/>
|
||||
<n-form-item>
|
||||
<n-checkbox v-model:checked="skipAscii">
|
||||
Skip Ascii characters
|
||||
</n-checkbox>
|
||||
</n-form-item>
|
||||
|
||||
<c-input-text v-model:value="inputText" multiline placeholder="e.g. 'Hello Avengers'" label="Enter text to convert to unicode" autosize autofocus raw-text test-id="text-to-unicode-input" />
|
||||
<c-input-text v-model:value="unicodeFromText" label="Unicode from your text" multiline raw-text readonly mt-2 placeholder="The unicode representation of your text will be here" test-id="text-to-unicode-output" />
|
||||
<div mt-2 flex justify-center>
|
||||
|
|
|
@ -151,7 +151,7 @@ function onSearchInput() {
|
|||
>
|
||||
<div flex-1 truncate>
|
||||
<slot name="displayed-value">
|
||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput">
|
||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full color-current lh-normal @input="onSearchInput">
|
||||
<span v-else-if="selectedOption" lh-normal>
|
||||
{{ selectedOption.label }}
|
||||
</span>
|
||||
|
|
|
@ -39,7 +39,7 @@ const headers = computed(() => {
|
|||
<template>
|
||||
<div class="relative overflow-x-auto rounded">
|
||||
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
|
||||
<thead v-if="!hideHeaders" class="bg-#ffffff uppercase text-gray-700 dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||
<thead v-if="!hideHeaders" class="bg-#ffffff text-gray-700 uppercase dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||
<tr>
|
||||
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
||||
{{ header.label }}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue