feat(Text to Unicode/Binary): encoding enhancements
Fix part of #1447 and #1072
This commit is contained in:
		
							parent
							
								
									08d977b8cd
								
							
						
					
					
						commit
						97e8ff6c5c
					
				| @ -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'] | ||||||
|  | |||||||
							
								
								
									
										14505
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14505
									
								
								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('') | function convertTextToUtf8Binary(text: string, { separator = ' ', base = 2 }: { separator?: string; base?: EncodingBase } = {}): string { | ||||||
|     .map(char => char.charCodeAt(0).toString(2).padStart(8, '0')) |   if (!text?.trim()) { | ||||||
|     .join(separator); |     return ''; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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 convertAsciiBinaryToText(binary: string): string { | function convertUtf8BinaryToText(binary: string, { base = 2 }: { base?: EncodingBase } = {}): string { | ||||||
|   const cleanBinary = binary.replace(/[^01]/g, ''); |   if (!binary?.trim()) { | ||||||
|  |     return ''; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let codepoints: number[] = []; | ||||||
|  |   if (base === 2) { | ||||||
|  |     const cleanBinary = binary.replace(/0b/g, '').replace(/[^01]/g, '').trim(); | ||||||
| 
 | 
 | ||||||
|     if (cleanBinary.length % 8) { |     if (cleanBinary.length % 8) { | ||||||
|       throw new Error('Invalid binary string'); |       throw new Error('Invalid binary string'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   return cleanBinary |     codepoints = cleanBinary | ||||||
|     .split(/(\d{8})/) |       .split(/([01]{8})/) | ||||||
|       .filter(Boolean) |       .filter(Boolean) | ||||||
|     .map(binary => String.fromCharCode(Number.parseInt(binary, 2))) |       .map(binary => Number.parseInt(binary, 2)); | ||||||
|     .join(''); |   } | ||||||
|  |   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,28 +1,62 @@ | |||||||
| <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" | ||||||
|  |       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="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> |       <div mt-2 flex justify-center> | ||||||
|         <c-button :disabled="!binaryFromText" @click="copyBinary()"> |         <c-button :disabled="!binaryFromText" @click="copyBinary()"> | ||||||
|           Copy binary to clipboard |           Copy binary to clipboard | ||||||
| @ -30,13 +64,32 @@ const { copy: copyText } = useCopy({ source: textFromBinary }); | |||||||
|       </div> |       </div> | ||||||
|     </c-card> |     </c-card> | ||||||
| 
 | 
 | ||||||
|   <c-card title="ASCII binary to text"> |     <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 | ||||||
|     <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="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> |       <div mt-2 flex justify-center> | ||||||
|         <c-button :disabled="!textFromBinary" @click="copyText()"> |         <c-button :disabled="!textFromBinary" @click="copyText()"> | ||||||
|           Copy text to clipboard |           Copy text to clipboard | ||||||
|         </c-button> |         </c-button> | ||||||
|       </div> |       </div> | ||||||
|     </c-card> |     </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…
	
		Reference in New Issue
	
	Block a user