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,
|
"watchTriggerable": true,
|
||||||
"watchWithFilter": true,
|
"watchWithFilter": true,
|
||||||
"whenever": 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 h: typeof import('vue')['h']
|
||||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||||
const inject: typeof import('vue')['inject']
|
const inject: typeof import('vue')['inject']
|
||||||
|
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||||
const isProxy: typeof import('vue')['isProxy']
|
const isProxy: typeof import('vue')['isProxy']
|
||||||
const isReactive: typeof import('vue')['isReactive']
|
const isReactive: typeof import('vue')['isReactive']
|
||||||
|
@ -65,6 +66,7 @@ declare global {
|
||||||
const onUpdated: typeof import('vue')['onUpdated']
|
const onUpdated: typeof import('vue')['onUpdated']
|
||||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||||
const provide: typeof import('vue')['provide']
|
const provide: typeof import('vue')['provide']
|
||||||
|
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||||
const reactify: typeof import('@vueuse/core')['reactify']
|
const reactify: typeof import('@vueuse/core')['reactify']
|
||||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||||
const reactive: typeof import('vue')['reactive']
|
const reactive: typeof import('vue')['reactive']
|
||||||
|
@ -128,6 +130,7 @@ declare global {
|
||||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||||
const useCached: typeof import('@vueuse/core')['useCached']
|
const useCached: typeof import('@vueuse/core')['useCached']
|
||||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||||
|
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||||
|
@ -326,6 +329,7 @@ declare module 'vue' {
|
||||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
@ -355,6 +359,7 @@ declare module 'vue' {
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||||
|
@ -418,6 +423,7 @@ declare module 'vue' {
|
||||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
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 useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
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 h: UnwrapRef<typeof import('vue')['h']>
|
||||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||||
|
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||||
|
@ -639,6 +646,7 @@ declare module '@vue/runtime-core' {
|
||||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||||
|
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
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 useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
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 useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
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']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDivider: typeof import('naive-ui')['NDivider']
|
NDivider: typeof import('naive-ui')['NDivider']
|
||||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||||
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
NH1: typeof import('naive-ui')['NH1']
|
NH1: typeof import('naive-ui')['NH1']
|
||||||
NH3: typeof import('naive-ui')['NH3']
|
NH3: typeof import('naive-ui')['NH3']
|
||||||
NIcon: typeof import('naive-ui')['NIcon']
|
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 />
|
<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 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 }}
|
{{ category }}
|
||||||
</div>
|
</div>
|
||||||
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
<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';
|
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 }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await page.goto('/text-to-binary');
|
await page.goto('/text-to-binary');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Has correct title', async ({ page }) => {
|
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 }) => {
|
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 }) => {
|
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();
|
const text = await page.getByTestId('binary-to-text-output').inputValue();
|
||||||
|
|
||||||
expect(text).toEqual('it-tools');
|
expect(text).toEqual('it-tools');
|
||||||
|
|
|
@ -1,32 +1,103 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
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('text-to-binary', () => {
|
||||||
describe('convertTextToAsciiBinary', () => {
|
const utf8Tests = [
|
||||||
it('a text string is converted to its ascii binary representation', () => {
|
{
|
||||||
expect(convertTextToAsciiBinary('A')).toBe('01000001');
|
text: '文字',
|
||||||
expect(convertTextToAsciiBinary('hello')).toBe('01101000 01100101 01101100 01101100 01101111');
|
binary: '11100110 10010110 10000111 11100101 10101101 10010111',
|
||||||
expect(convertTextToAsciiBinary('')).toBe('');
|
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', () => {
|
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', () => {
|
it('an ascii binary string is converted to its text representation', () => {
|
||||||
expect(convertAsciiBinaryToText('01101000 01100101 01101100 01101100 01101111')).toBe('hello');
|
expect(convertUtf8BinaryToText('01101000 01100101 01101100 01101100 01101111')).toBe('hello');
|
||||||
expect(convertAsciiBinaryToText('01000001')).toBe('A');
|
expect(convertUtf8BinaryToText('01101000 01100101 01101100 01101100 01101111', { base: 2 })).toBe('hello');
|
||||||
expect(convertTextToAsciiBinary('')).toBe('');
|
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', () => {
|
it('the given string is cleaned before conversion', () => {
|
||||||
expect(convertAsciiBinaryToText(' 01000 001garbage')).toBe('A');
|
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', () => {
|
it('throws an error if the given binary string is not an integer number of complete octets', () => {
|
||||||
expect(() => convertAsciiBinaryToText('010000011')).toThrow('Invalid binary string');
|
expect(() => convertUtf8BinaryToText('010000011')).toThrow('Invalid binary string');
|
||||||
expect(() => convertAsciiBinaryToText('1')).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 {
|
export type EncodingBase = 2 | 8 | 10 | 16;
|
||||||
return text
|
|
||||||
.split('')
|
|
||||||
.map(char => char.charCodeAt(0).toString(2).padStart(8, '0'))
|
|
||||||
.join(separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertAsciiBinaryToText(binary: string): string {
|
function convertTextToUtf8Binary(text: string, { separator = ' ', base = 2 }: { separator?: string; base?: EncodingBase } = {}): string {
|
||||||
const cleanBinary = binary.replace(/[^01]/g, '');
|
if (!text?.trim()) {
|
||||||
|
return '';
|
||||||
if (cleanBinary.length % 8) {
|
|
||||||
throw new Error('Invalid binary string');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanBinary
|
return [...new TextEncoder().encode(text)].map((char) => {
|
||||||
.split(/(\d{8})/)
|
const charInBase = char.toString(base);
|
||||||
.filter(Boolean)
|
if (base === 2) {
|
||||||
.map(binary => String.fromCharCode(Number.parseInt(binary, 2)))
|
return charInBase.padStart(8, '0');
|
||||||
.join('');
|
}
|
||||||
|
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">
|
<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 { withDefaultOnError } from '@/utils/defaults';
|
||||||
import { useCopy } from '@/composable/copy';
|
import { useCopy } from '@/composable/copy';
|
||||||
import { isNotThrowing } from '@/utils/boolean';
|
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 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 { copy: copyBinary } = useCopy({ source: binaryFromText });
|
||||||
|
|
||||||
const inputBinary = ref('');
|
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 = [
|
const inputBinaryValidationRules = [
|
||||||
{
|
{
|
||||||
validator: (value: string) => isNotThrowing(() => convertAsciiBinaryToText(value)),
|
validator: (value: string) => isNotThrowing(() => convertUtf8BinaryToText(value)),
|
||||||
message: 'Binary should be a valid ASCII binary string with multiples of 8 bits',
|
message: 'Binary should be a valid UTF-8 binary string with multiples of 8 bits',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { copy: copyText } = useCopy({ source: textFromBinary });
|
const { copy: copyText } = useCopy({ source: textFromBinary });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<c-card title="Text to ASCII binary">
|
<div>
|
||||||
<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-select
|
||||||
<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" />
|
v-model:value="base"
|
||||||
<div mt-2 flex justify-center>
|
label="Conversion Base:"
|
||||||
<c-button :disabled="!binaryFromText" @click="copyBinary()">
|
label-position="left"
|
||||||
Copy binary to clipboard
|
mb-2
|
||||||
</c-button>
|
:options="[
|
||||||
</div>
|
{ value: '2', label: 'Binary' },
|
||||||
</c-card>
|
{ value: '8', label: 'Octal' },
|
||||||
|
{ value: '10', label: 'Decimal' },
|
||||||
|
{ value: '16', label: 'Hexadecimal' },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
|
||||||
<c-card title="ASCII binary to text">
|
<c-card title="Text to UTF-8 binary">
|
||||||
<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
|
||||||
<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" />
|
v-model:value="inputText"
|
||||||
<div mt-2 flex justify-center>
|
multiline
|
||||||
<c-button :disabled="!textFromBinary" @click="copyText()">
|
placeholder="e.g. 'Hello world'"
|
||||||
Copy text to clipboard
|
label="Enter text to convert to binary"
|
||||||
</c-button>
|
autosize
|
||||||
</div>
|
autofocus
|
||||||
</c-card>
|
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>
|
</template>
|
||||||
|
|
|
@ -10,16 +10,18 @@ test.describe('Tool - Text to Unicode', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Text to unicode conversion', async ({ page }) => {
|
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();
|
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 }) => {
|
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();
|
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', () => {
|
describe('convertTextToUnicode', () => {
|
||||||
it('a text string is converted to unicode representation', () => {
|
it('a text string is converted to unicode representation', () => {
|
||||||
expect(convertTextToUnicode('A')).toBe('A');
|
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('');
|
expect(convertTextToUnicode('')).toBe('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -13,6 +24,16 @@ describe('text-to-unicode', () => {
|
||||||
describe('convertUnicodeToText', () => {
|
describe('convertUnicodeToText', () => {
|
||||||
it('an unicode string is converted to its text representation', () => {
|
it('an unicode string is converted to its text representation', () => {
|
||||||
expect(convertUnicodeToText('A')).toBe('A');
|
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('linke the string convert to unicode')).toBe('linke the string convert to unicode');
|
||||||
expect(convertUnicodeToText('')).toBe('');
|
expect(convertUnicodeToText('')).toBe('');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,66 @@
|
||||||
function convertTextToUnicode(text: string): string {
|
export type Encoding = 'htmldec' | 'htmlhex' | 'uniplus' | 'antiuni' | 'css' | 'python' | 'js' | 'utf16';
|
||||||
return text.split('').map(value => `&#${value.charCodeAt(0)};`).join('');
|
|
||||||
|
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 {
|
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 };
|
export { convertTextToUnicode, convertUnicodeToText };
|
||||||
|
|
|
@ -1,18 +1,46 @@
|
||||||
<script setup lang="ts">
|
<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';
|
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 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 { copy: copyUnicode } = useCopy({ source: unicodeFromText });
|
||||||
|
|
||||||
const inputUnicode = ref('');
|
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 });
|
const { copy: copyText } = useCopy({ source: textFromUnicode });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<c-card title="Text to Unicode">
|
<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="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" />
|
<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>
|
<div mt-2 flex justify-center>
|
||||||
|
|
|
@ -151,7 +151,7 @@ function onSearchInput() {
|
||||||
>
|
>
|
||||||
<div flex-1 truncate>
|
<div flex-1 truncate>
|
||||||
<slot name="displayed-value">
|
<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>
|
<span v-else-if="selectedOption" lh-normal>
|
||||||
{{ selectedOption.label }}
|
{{ selectedOption.label }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -39,7 +39,7 @@ const headers = computed(() => {
|
||||||
<template>
|
<template>
|
||||||
<div class="relative overflow-x-auto rounded">
|
<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">
|
<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>
|
<tr>
|
||||||
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
||||||
{{ header.label }}
|
{{ header.label }}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue