feat: add split by separator + order by numeric + no sort
Fix #764 #1279 #1090 Small screen UI Fix
This commit is contained in:
		
							parent
							
								
									327ff11a59
								
							
						
					
					
						commit
						7bafd4790e
					
				
							
								
								
									
										2
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -132,6 +132,7 @@ declare module '@vue/runtime-core' { | ||||
|     NCode: typeof import('naive-ui')['NCode'] | ||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||
|     NDivider: typeof import('naive-ui')['NDivider'] | ||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||
|     NForm: typeof import('naive-ui')['NForm'] | ||||
|     NFormItem: typeof import('naive-ui')['NFormItem'] | ||||
| @ -144,6 +145,7 @@ declare module '@vue/runtime-core' { | ||||
|     NMenu: typeof import('naive-ui')['NMenu'] | ||||
|     NScrollbar: typeof import('naive-ui')['NScrollbar'] | ||||
|     NSlider: typeof import('naive-ui')['NSlider'] | ||||
|     NSpace: typeof import('naive-ui')['NSpace'] | ||||
|     NSwitch: typeof import('naive-ui')['NSwitch'] | ||||
|     NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] | ||||
|     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] | ||||
|  | ||||
| @ -6,19 +6,11 @@ describe('list-converter', () => { | ||||
|   describe('convert', () => { | ||||
|     it('should convert a given list', () => { | ||||
|       const options: ConvertOptions = { | ||||
|         separator: ', ', | ||||
|         itemsSeparator: ', ', | ||||
|         trimItems: true, | ||||
|         removeDuplicates: true, | ||||
|         itemPrefix: '"', | ||||
|         itemSuffix: '"', | ||||
|         removeItemPrefix: '', | ||||
|         removeItemSuffix: '', | ||||
|         listPrefix: '', | ||||
|         listSuffix: '', | ||||
|         reverseList: false, | ||||
|         sortList: null, | ||||
|         lowerCase: false, | ||||
|         keepLineBreaks: false, | ||||
|       }; | ||||
|       const input = ` | ||||
|         1 | ||||
| @ -33,38 +25,21 @@ describe('list-converter', () => { | ||||
| 
 | ||||
|     it('should return an empty value for an empty input', () => { | ||||
|       const options: ConvertOptions = { | ||||
|         separator: ', ', | ||||
|         itemsSeparator: ', ', | ||||
|         trimItems: true, | ||||
|         removeDuplicates: true, | ||||
|         itemPrefix: '', | ||||
|         itemSuffix: '', | ||||
|         removeItemPrefix: '', | ||||
|         removeItemSuffix: '', | ||||
|         listPrefix: '', | ||||
|         listSuffix: '', | ||||
|         reverseList: false, | ||||
|         sortList: null, | ||||
|         lowerCase: false, | ||||
|         keepLineBreaks: false, | ||||
|       }; | ||||
|       expect(convert('', options)).toEqual(''); | ||||
|     }); | ||||
| 
 | ||||
|     it('should keep line breaks', () => { | ||||
|       const options: ConvertOptions = { | ||||
|         separator: '', | ||||
|         trimItems: true, | ||||
|         itemPrefix: '<li>', | ||||
|         itemSuffix: '</li>', | ||||
|         removeItemPrefix: '', | ||||
|         removeItemSuffix: '', | ||||
|         listPrefix: '<ul>', | ||||
|         listSuffix: '</ul>', | ||||
|         keepLineBreaks: true, | ||||
|         lowerCase: false, | ||||
|         removeDuplicates: false, | ||||
|         reverseList: false, | ||||
|         sortList: null, | ||||
|       }; | ||||
|       const input = ` | ||||
|         1 | ||||
| @ -81,30 +56,61 @@ describe('list-converter', () => { | ||||
| 
 | ||||
|     it('should remove prefix and suffix', () => { | ||||
|       const options: ConvertOptions = { | ||||
|         separator: '', | ||||
|         trimItems: true, | ||||
|         itemPrefix: '', | ||||
|         itemSuffix: '', | ||||
|         removeItemPrefix: '\<li\>', | ||||
|         removeItemSuffix: '\</li\>', | ||||
|         listPrefix: '', | ||||
|         listSuffix: '', | ||||
|         removeItemPrefix: '<li>', | ||||
|         removeItemSuffix: '</li>', | ||||
|         keepLineBreaks: true, | ||||
|         lowerCase: false, | ||||
|         removeDuplicates: false, | ||||
|         reverseList: false, | ||||
|         sortList: null, | ||||
|       }; | ||||
|       const input = ` | ||||
| <li>1</li> | ||||
| <li>2</li> | ||||
| <li>3</li> | ||||
|         `;
 | ||||
|       const expected = ` | ||||
| 1 | ||||
|       const expected = `1
 | ||||
| 2 | ||||
| 3`;
 | ||||
|       expect(convert(input, options)).toEqual(expected); | ||||
|     }); | ||||
| 
 | ||||
|     it('should split by separator', () => { | ||||
|       const options: ConvertOptions = { | ||||
|         trimItems: true, | ||||
|         keepLineBreaks: true, | ||||
|         splitBySeparator: ',', | ||||
|       }; | ||||
|       const input = '1,2,3'; | ||||
|       const expected = `1
 | ||||
| 2 | ||||
| 3`;
 | ||||
|       expect(convert(input, options)).toEqual(expected); | ||||
|     }); | ||||
| 
 | ||||
|     it('should sort by asc-num', () => { | ||||
|       const options: ConvertOptions = { | ||||
|         trimItems: true, | ||||
|         keepLineBreaks: true, | ||||
|         sortList: 'asc-num', | ||||
|       }; | ||||
|       const input = `3
 | ||||
| 20 | ||||
| 1`;
 | ||||
|       const expected = `1
 | ||||
| 3 | ||||
| `;
 | ||||
| 20`;
 | ||||
|       expect(convert(input, options)).toEqual(expected); | ||||
|     }); | ||||
|     it('should sort by desc', () => { | ||||
|       const options: ConvertOptions = { | ||||
|         trimItems: true, | ||||
|         keepLineBreaks: true, | ||||
|         sortList: 'desc', | ||||
|       }; | ||||
|       const input = `1
 | ||||
| 20 | ||||
| 3`;
 | ||||
|       const expected = `3
 | ||||
| 20 | ||||
| 1`;
 | ||||
|       expect(convert(input, options)).toEqual(expected); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
| @ -4,7 +4,7 @@ import { byOrder } from '@/utils/array'; | ||||
| 
 | ||||
| export { convert }; | ||||
| 
 | ||||
| function whenever<T, R>(condition: boolean, fn: (value: T) => R) { | ||||
| function whenever<T, R>(condition: boolean | undefined, fn: (value: T) => R) { | ||||
|   return (value: T) => | ||||
|     condition ? fn(value) : value; | ||||
| } | ||||
| @ -12,18 +12,20 @@ function whenever<T, R>(condition: boolean, fn: (value: T) => R) { | ||||
| function convert(list: string, options: ConvertOptions): string { | ||||
|   const lineBreak = options.keepLineBreaks ? '\n' : ''; | ||||
| 
 | ||||
|   const splitSep = options.splitBySeparator ? `${options.splitBySeparator}|` : ''; | ||||
|   const splitRegExp = new RegExp(`(?:${splitSep}\\n)`, 'g'); | ||||
|   return _.chain(list) | ||||
|     .thru(whenever(options.lowerCase, text => text.toLowerCase())) | ||||
|     .split('\n') | ||||
|     .split(splitRegExp) | ||||
|     .thru(whenever(options.removeDuplicates, _.uniq)) | ||||
|     .thru(whenever(options.reverseList, _.reverse)) | ||||
|     .thru(whenever(!_.isNull(options.sortList), parts => parts.sort(byOrder({ order: options.sortList })))) | ||||
|     .map(whenever(options.trimItems, _.trim)) | ||||
|     .thru(whenever(!!options.sortList, parts => parts.sort(byOrder({ order: options.sortList })))) | ||||
|     .without('') | ||||
|     .map(p => options.removeItemPrefix ? p.replace(new RegExp(`^${options.removeItemPrefix}`, 'g'), '') : p) | ||||
|     .map(p => options.removeItemSuffix ? p.replace(new RegExp(`${options.removeItemSuffix}$`, 'g'), '') : p) | ||||
|     .map(p => options.itemPrefix + p + options.itemSuffix) | ||||
|     .join(options.separator + lineBreak) | ||||
|     .thru(text => [options.listPrefix, text, options.listSuffix].join(lineBreak)) | ||||
|     .map(p => (options.itemPrefix || '') + p + (options.itemSuffix || '')) | ||||
|     .join((options.itemsSeparator || '') + lineBreak) | ||||
|     .thru(text => [options.listPrefix, text, options.listSuffix].filter(l => l).join(lineBreak)) | ||||
|     .value(); | ||||
| } | ||||
|  | ||||
| @ -1,17 +1,18 @@ | ||||
| export type SortOrder = 'asc' | 'desc' | null; | ||||
| export type SortOrder = null | 'asc' | 'desc' | 'asc-num' | 'desc-num' | 'asc-bin' | 'desc-bin' | 'asc-upper' | 'desc-upper'; | ||||
| 
 | ||||
| export interface ConvertOptions { | ||||
|   lowerCase: boolean | ||||
|   trimItems: boolean | ||||
|   itemPrefix: string | ||||
|   itemSuffix: string | ||||
|   removeItemPrefix: string | ||||
|   removeItemSuffix: string | ||||
|   listPrefix: string | ||||
|   listSuffix: string | ||||
|   reverseList: boolean | ||||
|   sortList: SortOrder | ||||
|   removeDuplicates: boolean | ||||
|   separator: string | ||||
|   keepLineBreaks: boolean | ||||
|   lowerCase?: boolean | ||||
|   trimItems?: boolean | ||||
|   itemPrefix?: string | ||||
|   itemSuffix?: string | ||||
|   removeItemPrefix?: string | ||||
|   removeItemSuffix?: string | ||||
|   listPrefix?: string | ||||
|   listSuffix?: string | ||||
|   reverseList?: boolean | ||||
|   sortList?: SortOrder | ||||
|   removeDuplicates?: boolean | ||||
|   itemsSeparator?: string | ||||
|   splitBySeparator?: string | ||||
|   keepLineBreaks?: boolean | ||||
| } | ||||
|  | ||||
| @ -4,15 +4,41 @@ import { convert } from './list-converter.models'; | ||||
| import type { ConvertOptions } from './list-converter.types'; | ||||
| 
 | ||||
| const sortOrderOptions = [ | ||||
|   { | ||||
|     label: 'No Sort', | ||||
|     value: null, | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort ascending', | ||||
|     value: 'asc', | ||||
|     disabled: false, | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort descending', | ||||
|     value: 'desc', | ||||
|     disabled: false, | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort asc (Numeric)', | ||||
|     value: 'asc-num', | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort desc (Numeric)', | ||||
|     value: 'desc-num', | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort asc (Upper)', | ||||
|     value: 'asc-upper', | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort desc (Upper)', | ||||
|     value: 'desc-upper', | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort asc (Binary)', | ||||
|     value: 'asc-bin', | ||||
|   }, | ||||
|   { | ||||
|     label: 'Sort desc (Binary)', | ||||
|     value: 'desc-bin', | ||||
|   }, | ||||
| ]; | ||||
| 
 | ||||
| @ -29,7 +55,8 @@ const conversionConfig = useStorage<ConvertOptions>('list-converter:conversionCo | ||||
|   listSuffix: '', | ||||
|   reverseList: false, | ||||
|   sortList: null, | ||||
|   separator: ', ', | ||||
|   itemsSeparator: ', ', | ||||
|   splitBySeparator: '', | ||||
| }); | ||||
| 
 | ||||
| function transformer(value: string) { | ||||
| @ -41,7 +68,7 @@ function transformer(value: string) { | ||||
|   <div style="flex: 0 0 100%"> | ||||
|     <div style="margin: 0 auto; max-width: 600px"> | ||||
|       <c-card> | ||||
|         <div flex> | ||||
|         <n-space> | ||||
|           <div> | ||||
|             <n-form-item label="Trim list items" label-placement="left" label-width="150" :show-feedback="false" mb-2> | ||||
|               <n-switch v-model:value="conversionConfig.trimItems" /> | ||||
| @ -62,7 +89,7 @@ function transformer(value: string) { | ||||
|               <n-switch v-model:value="conversionConfig.keepLineBreaks" /> | ||||
|             </n-form-item> | ||||
|           </div> | ||||
|           <div flex-1> | ||||
|           <div> | ||||
|             <c-select | ||||
|               v-model:value="conversionConfig.sortList" | ||||
|               label="Sort list" | ||||
| @ -78,13 +105,23 @@ function transformer(value: string) { | ||||
|             /> | ||||
| 
 | ||||
|             <c-input-text | ||||
|               v-model:value="conversionConfig.separator" | ||||
|               label="Separator" | ||||
|               v-model:value="conversionConfig.itemsSeparator" | ||||
|               label="Items Separator" | ||||
|               label-position="left" | ||||
|               label-width="120px" | ||||
|               label-align="right" | ||||
|               mb-2 | ||||
|               placeholder="," | ||||
|               placeholder="Items separator" | ||||
|             /> | ||||
| 
 | ||||
|             <c-input-text | ||||
|               v-model:value="conversionConfig.splitBySeparator" | ||||
|               label="Split Separator" | ||||
|               label-position="left" | ||||
|               label-width="120px" | ||||
|               label-align="right" | ||||
|               mb-2 | ||||
|               placeholder="Separator for splitting" | ||||
|             /> | ||||
| 
 | ||||
|             <n-form-item label="Unwrap item" label-placement="left" label-width="120" :show-feedback="false" mb-2> | ||||
| @ -125,7 +162,7 @@ function transformer(value: string) { | ||||
|               /> | ||||
|             </n-form-item> | ||||
|           </div> | ||||
|         </div> | ||||
|         </n-space> | ||||
|       </c-card> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
							
								
								
									
										25
									
								
								src/utils/array.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/utils/array.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| import { describe, expect, it } from 'vitest'; | ||||
| import { type SortOrder, byOrder } from './array'; | ||||
| 
 | ||||
| describe('array utils', () => { | ||||
|   describe('byOrder', () => { | ||||
|     it('should sort correctly', () => { | ||||
|       const sortBy = (array: string[], order: SortOrder) => { | ||||
|         return array.sort(byOrder({ order })); | ||||
|       }; | ||||
| 
 | ||||
|       const strings = ['a', 'A', 'b', 'B', 'á', '1', '2', '10', '一', '阿']; | ||||
| 
 | ||||
|       expect(sortBy(strings, null)).to.eql(strings); | ||||
|       expect(sortBy(strings, undefined)).to.eql(strings); | ||||
|       expect(sortBy(strings, 'asc')).to.eql(['1', '10', '2', 'a', 'A', 'á', 'b', 'B', '一', '阿']); | ||||
|       expect(sortBy(strings, 'asc-num')).to.eql(['1', '2', '10', 'a', 'A', 'á', 'b', 'B', '一', '阿']); | ||||
|       expect(sortBy(strings, 'asc-bin')).to.eql(['1', '10', '2', 'A', 'B', 'a', 'b', 'á', '一', '阿']); | ||||
|       expect(sortBy(strings, 'asc-upper')).to.eql(['1', '10', '2', 'A', 'a', 'á', 'B', 'b', '一', '阿']); | ||||
|       expect(sortBy(strings, 'desc')).to.eql(['阿', '一', 'B', 'b', 'á', 'A', 'a', '2', '10', '1']); | ||||
|       expect(sortBy(strings, 'desc-num')).to.eql(['阿', '一', 'B', 'b', 'á', 'A', 'a', '10', '2', '1']); | ||||
|       expect(sortBy(strings, 'desc-bin')).to.eql(['阿', '一', 'á', 'b', 'a', 'B', 'A', '2', '10', '1']); | ||||
|       expect(sortBy(strings, 'desc-upper')).to.eql(['阿', '一', 'b', 'B', 'á', 'a', 'A', '2', '10', '1']); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @ -1,6 +1,29 @@ | ||||
| export { byOrder }; | ||||
| export type SortOrder = 'asc' | 'desc' | 'asc-num' | 'desc-num' | 'asc-bin' | 'desc-bin' | 'asc-upper' | 'desc-upper' | null | undefined; | ||||
| 
 | ||||
| export function byOrder({ order }: { order: SortOrder }) { | ||||
|   if (order === 'asc-bin' || order === 'desc-bin') { | ||||
|     return (a: string, b: string) => { | ||||
|       const compare = a > b ? 1 : (a < b ? -1 : 0); // NOSONAR
 | ||||
|       return order === 'asc-bin' ? compare : -compare; | ||||
|     }; | ||||
|   } | ||||
|   if (order === 'asc-num' || order === 'desc-num') { | ||||
|     return (a: string, b: string) => { | ||||
|       const compare = a.localeCompare(b, undefined, { | ||||
|         numeric: true, | ||||
|       }); | ||||
|       return order === 'asc-num' ? compare : -compare; | ||||
|     }; | ||||
|   } | ||||
|   if (order === 'asc-upper' || order === 'desc-upper') { | ||||
|     return (a: string, b: string) => { | ||||
|       const compare = a.localeCompare(b, undefined, { | ||||
|         caseFirst: 'upper', | ||||
|       }); | ||||
|       return order === 'asc-upper' ? compare : -compare; | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
| function byOrder({ order }: { order: 'asc' | 'desc' | null | undefined }) { | ||||
|   return (a: string, b: string) => { | ||||
|     return order === 'asc' ? a.localeCompare(b) : b.localeCompare(a); | ||||
|   }; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user