diff --git a/components.d.ts b/components.d.ts index 89f41f808..f1a030728 100644 --- a/components.d.ts +++ b/components.d.ts @@ -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'] diff --git a/src/tools/list-converter/list-converter.models.test.ts b/src/tools/list-converter/list-converter.models.test.ts index d6c87773b..6229c5c75 100644 --- a/src/tools/list-converter/list-converter.models.test.ts +++ b/src/tools/list-converter/list-converter.models.test.ts @@ -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: '
  • ', itemSuffix: '
  • ', - removeItemPrefix: '', - removeItemSuffix: '', listPrefix: '', 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: '\', - removeItemSuffix: '\', - listPrefix: '', - listSuffix: '', + removeItemPrefix: '
  • ', + removeItemSuffix: '
  • ', keepLineBreaks: true, - lowerCase: false, - removeDuplicates: false, - reverseList: false, - sortList: null, }; const input = `
  • 1
  • 2
  • 3
  • `; - 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); }); }); diff --git a/src/tools/list-converter/list-converter.models.ts b/src/tools/list-converter/list-converter.models.ts index 5c047293c..9c88128bf 100644 --- a/src/tools/list-converter/list-converter.models.ts +++ b/src/tools/list-converter/list-converter.models.ts @@ -4,7 +4,7 @@ import { byOrder } from '@/utils/array'; export { convert }; -function whenever(condition: boolean, fn: (value: T) => R) { +function whenever(condition: boolean | undefined, fn: (value: T) => R) { return (value: T) => condition ? fn(value) : value; } @@ -14,16 +14,16 @@ function convert(list: string, options: ConvertOptions): string { return _.chain(list) .thru(whenever(options.lowerCase, text => text.toLowerCase())) - .split('\n') + .split(new RegExp(`[${options.splitBySeparator || ''}\n]`, 'g')) .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(); } diff --git a/src/tools/list-converter/list-converter.types.ts b/src/tools/list-converter/list-converter.types.ts index 448f1a5a7..7487f1e7c 100644 --- a/src/tools/list-converter/list-converter.types.ts +++ b/src/tools/list-converter/list-converter.types.ts @@ -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 } diff --git a/src/tools/list-converter/list-converter.vue b/src/tools/list-converter/list-converter.vue index 58c581880..8e27f304e 100644 --- a/src/tools/list-converter/list-converter.vue +++ b/src/tools/list-converter/list-converter.vue @@ -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('list-converter:conversionCo listSuffix: '', reverseList: false, sortList: null, - separator: ', ', + itemsSeparator: ', ', + splitBySeparator: '', }); function transformer(value: string) { @@ -41,7 +68,7 @@ function transformer(value: string) {
    -
    +
    @@ -62,7 +89,7 @@ function transformer(value: string) {
    -
    +
    + + @@ -125,7 +162,7 @@ function transformer(value: string) { />
    -
    +
    diff --git a/src/utils/array.test.ts b/src/utils/array.test.ts new file mode 100644 index 000000000..e8d13e2dd --- /dev/null +++ b/src/utils/array.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it } from 'vitest'; +import { type SortOrder, byOrder } from './array'; + +describe('array utils', () => { + describe('byOrder', () => { + it('should sort correctly', () => { + const sortBy = (array: any[], order: SortOrder) => 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', + ]); + }); + }); +}); diff --git a/src/utils/array.ts b/src/utils/array.ts index 15b3506dc..bf1903a38 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -1,7 +1,23 @@ -export { byOrder }; +export type SortOrder = 'asc' | 'desc' | 'asc-num' | 'desc-num' | 'asc-bin' | 'desc-bin' | 'asc-upper' | 'desc-upper' | null | undefined; -function byOrder({ order }: { order: 'asc' | 'desc' | null | undefined }) { +export function byOrder({ order }: { order: SortOrder }) { return (a: string, b: string) => { + if (order === 'asc-bin' || order === 'desc-bin') { + const compare = a > b ? 1 : a < b ? -1 : 0; + return order === 'asc-bin' ? compare : -compare; + } + if (order === 'asc-num' || order === 'desc-num') { + const compare = a.localeCompare(b, undefined, { + numeric: true, + }); + return order === 'asc-num' ? compare : -compare; + } + if (order === 'asc-upper' || order === 'desc-upper') { + const compare = a.localeCompare(b, undefined, { + caseFirst: 'upper', + }); + return order === 'asc-upper' ? compare : -compare; + } return order === 'asc' ? a.localeCompare(b) : b.localeCompare(a); }; }