diff --git a/components.d.ts b/components.d.ts index 3e65c3cc..7408454c 100644 --- a/components.d.ts +++ b/components.d.ts @@ -141,8 +141,10 @@ declare module '@vue/runtime-core' { NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] + NScrollbar: typeof import('naive-ui')['NScrollbar'] + NSlider: typeof import('naive-ui')['NSlider'] NSpace: typeof import('naive-ui')['NSpace'] - NTable: typeof import('naive-ui')['NTable'] + 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'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.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 abbc43c2..6229c5c7 100644 --- a/src/tools/list-converter/list-converter.models.test.ts +++ b/src/tools/list-converter/list-converter.models.test.ts @@ -6,17 +6,11 @@ describe('list-converter', () => { describe('convert', () => { it('should convert a given list', () => { const options: ConvertOptions = { - separator: ', ', + itemsSeparator: ', ', trimItems: true, removeDuplicates: true, itemPrefix: '"', itemSuffix: '"', - listPrefix: '', - listSuffix: '', - reverseList: false, - sortList: null, - lowerCase: false, - keepLineBreaks: false, }; const input = ` 1 @@ -31,34 +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: '', - 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: '
  • ', listPrefix: '', keepLineBreaks: true, - lowerCase: false, - removeDuplicates: false, - reverseList: false, - sortList: null, }; const input = ` 1 @@ -72,5 +53,65 @@ describe('list-converter', () => { `; expect(convert(input, options)).toEqual(expected); }); + + it('should remove prefix and suffix', () => { + const options: ConvertOptions = { + trimItems: true, + removeItemPrefix: '
  • ', + removeItemSuffix: '
  • ', + keepLineBreaks: true, + }; + const input = ` +
  • 1
  • +
  • 2
  • +
  • 3
  • + `; + 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 548baa28..e76c9ed4 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; } @@ -12,16 +12,20 @@ function whenever(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.itemPrefix + p + options.itemSuffix) - .join(options.separator + lineBreak) - .thru(text => [options.listPrefix, text, options.listSuffix].join(lineBreak)) + .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.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 3f9ea3f8..7487f1e7 100644 --- a/src/tools/list-converter/list-converter.types.ts +++ b/src/tools/list-converter/list-converter.types.ts @@ -1,15 +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 - 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 19dd30e5..8e27f304 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', }, ]; @@ -23,11 +49,14 @@ const conversionConfig = useStorage('list-converter:conversionCo keepLineBreaks: false, itemPrefix: '', itemSuffix: '', + removeItemPrefix: '', + removeItemSuffix: '', listPrefix: '', listSuffix: '', reverseList: false, sortList: null, - separator: ', ', + itemsSeparator: ', ', + splitBySeparator: '', }); function transformer(value: string) { @@ -39,7 +68,7 @@ function transformer(value: string) {
    -
    +
    @@ -60,7 +89,7 @@ function transformer(value: string) {
    -
    +
    + + + + + + +
    -
    +
    diff --git a/src/utils/array.test.ts b/src/utils/array.test.ts new file mode 100644 index 00000000..a521c2fd --- /dev/null +++ b/src/utils/array.test.ts @@ -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']); + }); + }); +}); diff --git a/src/utils/array.ts b/src/utils/array.ts index 15b3506d..e9a875c8 100644 --- a/src/utils/array.ts +++ b/src/utils/array.ts @@ -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); };