feat(Url Encoder): enhance encoding options
Handles RFC3986 - URIComponent ; RFC5987 (Link - Content-Disposition) and others Fix #1445
This commit is contained in:
		
							parent
							
								
									08d977b8cd
								
							
						
					
					
						commit
						55b4a8ce57
					
				
							
								
								
									
										14254
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14254
									
								
								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,11 +1,10 @@ | |||||||
| import { Link } from '@vicons/tabler'; | import { Link } from '@vicons/tabler'; | ||||||
| import { defineTool } from '../tool'; | import { defineTool } from '../tool'; | ||||||
| import { translate } from '@/plugins/i18n.plugin'; |  | ||||||
| 
 | 
 | ||||||
| export const tool = defineTool({ | export const tool = defineTool({ | ||||||
|   name: translate('tools.url-encoder.title'), |   name: 'Encode/decode url formatted strings', | ||||||
|   path: '/url-encoder', |   path: '/url-encoder', | ||||||
|   description: translate('tools.url-encoder.description'), |   description: 'Encode to url-encoded format (also known as "percent-encoded") or decode from it.', | ||||||
|   keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'], |   keywords: ['url', 'encode', 'decode', 'percent', '%20', 'format'], | ||||||
|   component: () => import('./url-encoder.vue'), |   component: () => import('./url-encoder.vue'), | ||||||
|   icon: Link, |   icon: Link, | ||||||
|  | |||||||
| @ -1,17 +1,82 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import { useCopy } from '@/composable/copy'; | import { useCopy } from '@/composable/copy'; | ||||||
|  | import { useQueryParamOrStorage } from '@/composable/queryParams'; | ||||||
| import { useValidation } from '@/composable/validation'; | import { useValidation } from '@/composable/validation'; | ||||||
| import { isNotThrowing } from '@/utils/boolean'; | import { isNotThrowing } from '@/utils/boolean'; | ||||||
| import { withDefaultOnError } from '@/utils/defaults'; | import { withDefaultOnError } from '@/utils/defaults'; | ||||||
| 
 | 
 | ||||||
|  | const encodings = [ | ||||||
|  |   { value: 'URIComponent', label: 'URIComponent' }, | ||||||
|  |   { value: 'RFC3986URIComponent', label: 'RFC3986 - URIComponent' }, | ||||||
|  |   { value: 'RFC5987', label: 'RFC5987 (Link - Content-Disposition)' }, | ||||||
|  |   { value: 'URI', label: 'URI' }, | ||||||
|  |   { value: 'RFC3986URI', label: 'RFC3986 - URI' }, | ||||||
|  | ]; | ||||||
|  | const encoding = useQueryParamOrStorage({ name: 'enc', storageName: 'url-encode:enc', defaultValue: 'URIComponent' }); | ||||||
|  | 
 | ||||||
|  | function encodeRFC3986URIComponent(str: string) { | ||||||
|  |   return encodeURIComponent(str).replace( | ||||||
|  |     /[!'()*]/g, | ||||||
|  |     (c: string) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function encodeRFC5987ValueChars(str: string) { | ||||||
|  |   return ( | ||||||
|  |     encodeURIComponent(str) | ||||||
|  |       // The following creates the sequences %27 %28 %29 %2A (Note that | ||||||
|  |       // the valid encoding of "*" is %2A, which necessitates calling | ||||||
|  |       // toUpperCase() to properly encode). Although RFC3986 reserves "!", | ||||||
|  |       // RFC5987 does not, so we do not need to escape it. | ||||||
|  |       .replace( | ||||||
|  |         /['()*]/g, | ||||||
|  |         c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`, | ||||||
|  |       ) | ||||||
|  |       // The following are not required for percent-encoding per RFC5987, | ||||||
|  |       // so we can allow for a little better readability over the wire: |`^ | ||||||
|  |       .replace(/%(7C|60|5E)/g, (str, hex) => | ||||||
|  |         String.fromCharCode(Number.parseInt(hex, 16)), | ||||||
|  |       ) | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function encodeRFC3986URI(str: string) { | ||||||
|  |   return encodeURI(str) | ||||||
|  |     .replace(/%5B/g, '[') | ||||||
|  |     .replace(/%5D/g, ']') | ||||||
|  |     .replace( | ||||||
|  |       /[!'()*]/g, | ||||||
|  |       c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`, | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function encode(str: string) { | ||||||
|  |   if (encoding.value === 'URIComponent') { | ||||||
|  |     return encodeURIComponent(str); | ||||||
|  |   } | ||||||
|  |   if (encoding.value === 'RFC3986URIComponent') { | ||||||
|  |     return encodeRFC3986URIComponent(str); | ||||||
|  |   } | ||||||
|  |   if (encoding.value === 'RFC5987') { | ||||||
|  |     return encodeRFC5987ValueChars(str); | ||||||
|  |   } | ||||||
|  |   if (encoding.value === 'URI') { | ||||||
|  |     return encodeURI(str); | ||||||
|  |   } | ||||||
|  |   if (encoding.value === 'RFC3986URI') { | ||||||
|  |     return encodeRFC3986URI(str); | ||||||
|  |   } | ||||||
|  |   return str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const encodeInput = ref('Hello world :)'); | const encodeInput = ref('Hello world :)'); | ||||||
| const encodeOutput = computed(() => withDefaultOnError(() => encodeURIComponent(encodeInput.value), '')); | const encodeOutput = computed(() => withDefaultOnError(() => encode(encodeInput.value), '')); | ||||||
| 
 | 
 | ||||||
| const encodedValidation = useValidation({ | const encodedValidation = useValidation({ | ||||||
|   source: encodeInput, |   source: encodeInput, | ||||||
|   rules: [ |   rules: [ | ||||||
|     { |     { | ||||||
|       validator: value => isNotThrowing(() => encodeURIComponent(value)), |       validator: value => isNotThrowing(() => encode(value)), | ||||||
|       message: 'Impossible to parse this string', |       message: 'Impossible to parse this string', | ||||||
|     }, |     }, | ||||||
|   ], |   ], | ||||||
| @ -23,7 +88,7 @@ const decodeInput = ref('Hello%20world%20%3A)'); | |||||||
| const decodeOutput = computed(() => withDefaultOnError(() => decodeURIComponent(decodeInput.value), '')); | const decodeOutput = computed(() => withDefaultOnError(() => decodeURIComponent(decodeInput.value), '')); | ||||||
| 
 | 
 | ||||||
| const decodeValidation = useValidation({ | const decodeValidation = useValidation({ | ||||||
|   source: decodeInput, |   source: encodeInput, | ||||||
|   rules: [ |   rules: [ | ||||||
|     { |     { | ||||||
|       validator: value => isNotThrowing(() => decodeURIComponent(value)), |       validator: value => isNotThrowing(() => decodeURIComponent(value)), | ||||||
| @ -37,6 +102,15 @@ const { copy: copyDecoded } = useCopy({ source: decodeOutput, text: 'Decoded str | |||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <c-card title="Encode"> |   <c-card title="Encode"> | ||||||
|  |     <c-select | ||||||
|  |       v-model:value="encoding" | ||||||
|  |       label="Encoding Kind:" | ||||||
|  |       label-position="left" | ||||||
|  |       :options="encodings" | ||||||
|  |     /> | ||||||
|  | 
 | ||||||
|  |     <n-divider /> | ||||||
|  | 
 | ||||||
|     <c-input-text |     <c-input-text | ||||||
|       v-model:value="encodeInput" |       v-model:value="encodeInput" | ||||||
|       label="Your string :" |       label="Your string :" | ||||||
|  | |||||||
| @ -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