Skip to content

Commit 7bafd47

Browse files
committed
feat: add split by separator + order by numeric + no sort
Fix CorentinTh#764 CorentinTh#1279 CorentinTh#1090 Small screen UI Fix
1 parent 327ff11 commit 7bafd47

File tree

7 files changed

+168
-72
lines changed

7 files changed

+168
-72
lines changed

components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ declare module '@vue/runtime-core' {
132132
NCode: typeof import('naive-ui')['NCode']
133133
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
134134
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
135+
NDivider: typeof import('naive-ui')['NDivider']
135136
NEllipsis: typeof import('naive-ui')['NEllipsis']
136137
NForm: typeof import('naive-ui')['NForm']
137138
NFormItem: typeof import('naive-ui')['NFormItem']
@@ -144,6 +145,7 @@ declare module '@vue/runtime-core' {
144145
NMenu: typeof import('naive-ui')['NMenu']
145146
NScrollbar: typeof import('naive-ui')['NScrollbar']
146147
NSlider: typeof import('naive-ui')['NSlider']
148+
NSpace: typeof import('naive-ui')['NSpace']
147149
NSwitch: typeof import('naive-ui')['NSwitch']
148150
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
149151
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']

src/tools/list-converter/list-converter.models.test.ts

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,11 @@ describe('list-converter', () => {
66
describe('convert', () => {
77
it('should convert a given list', () => {
88
const options: ConvertOptions = {
9-
separator: ', ',
9+
itemsSeparator: ', ',
1010
trimItems: true,
1111
removeDuplicates: true,
1212
itemPrefix: '"',
1313
itemSuffix: '"',
14-
removeItemPrefix: '',
15-
removeItemSuffix: '',
16-
listPrefix: '',
17-
listSuffix: '',
18-
reverseList: false,
19-
sortList: null,
20-
lowerCase: false,
21-
keepLineBreaks: false,
2214
};
2315
const input = `
2416
1
@@ -33,38 +25,21 @@ describe('list-converter', () => {
3325

3426
it('should return an empty value for an empty input', () => {
3527
const options: ConvertOptions = {
36-
separator: ', ',
28+
itemsSeparator: ', ',
3729
trimItems: true,
3830
removeDuplicates: true,
39-
itemPrefix: '',
40-
itemSuffix: '',
41-
removeItemPrefix: '',
42-
removeItemSuffix: '',
43-
listPrefix: '',
44-
listSuffix: '',
45-
reverseList: false,
46-
sortList: null,
47-
lowerCase: false,
48-
keepLineBreaks: false,
4931
};
5032
expect(convert('', options)).toEqual('');
5133
});
5234

5335
it('should keep line breaks', () => {
5436
const options: ConvertOptions = {
55-
separator: '',
5637
trimItems: true,
5738
itemPrefix: '<li>',
5839
itemSuffix: '</li>',
59-
removeItemPrefix: '',
60-
removeItemSuffix: '',
6140
listPrefix: '<ul>',
6241
listSuffix: '</ul>',
6342
keepLineBreaks: true,
64-
lowerCase: false,
65-
removeDuplicates: false,
66-
reverseList: false,
67-
sortList: null,
6843
};
6944
const input = `
7045
1
@@ -81,30 +56,61 @@ describe('list-converter', () => {
8156

8257
it('should remove prefix and suffix', () => {
8358
const options: ConvertOptions = {
84-
separator: '',
8559
trimItems: true,
86-
itemPrefix: '',
87-
itemSuffix: '',
88-
removeItemPrefix: '\<li\>',
89-
removeItemSuffix: '\</li\>',
90-
listPrefix: '',
91-
listSuffix: '',
60+
removeItemPrefix: '<li>',
61+
removeItemSuffix: '</li>',
9262
keepLineBreaks: true,
93-
lowerCase: false,
94-
removeDuplicates: false,
95-
reverseList: false,
96-
sortList: null,
9763
};
9864
const input = `
9965
<li>1</li>
10066
<li>2</li>
10167
<li>3</li>
10268
`;
103-
const expected = `
104-
1
69+
const expected = `1
10570
2
71+
3`;
72+
expect(convert(input, options)).toEqual(expected);
73+
});
74+
75+
it('should split by separator', () => {
76+
const options: ConvertOptions = {
77+
trimItems: true,
78+
keepLineBreaks: true,
79+
splitBySeparator: ',',
80+
};
81+
const input = '1,2,3';
82+
const expected = `1
83+
2
84+
3`;
85+
expect(convert(input, options)).toEqual(expected);
86+
});
87+
88+
it('should sort by asc-num', () => {
89+
const options: ConvertOptions = {
90+
trimItems: true,
91+
keepLineBreaks: true,
92+
sortList: 'asc-num',
93+
};
94+
const input = `3
95+
20
96+
1`;
97+
const expected = `1
10698
3
107-
`;
99+
20`;
100+
expect(convert(input, options)).toEqual(expected);
101+
});
102+
it('should sort by desc', () => {
103+
const options: ConvertOptions = {
104+
trimItems: true,
105+
keepLineBreaks: true,
106+
sortList: 'desc',
107+
};
108+
const input = `1
109+
20
110+
3`;
111+
const expected = `3
112+
20
113+
1`;
108114
expect(convert(input, options)).toEqual(expected);
109115
});
110116
});

src/tools/list-converter/list-converter.models.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,28 @@ import { byOrder } from '@/utils/array';
44

55
export { convert };
66

7-
function whenever<T, R>(condition: boolean, fn: (value: T) => R) {
7+
function whenever<T, R>(condition: boolean | undefined, fn: (value: T) => R) {
88
return (value: T) =>
99
condition ? fn(value) : value;
1010
}
1111

1212
function convert(list: string, options: ConvertOptions): string {
1313
const lineBreak = options.keepLineBreaks ? '\n' : '';
1414

15+
const splitSep = options.splitBySeparator ? `${options.splitBySeparator}|` : '';
16+
const splitRegExp = new RegExp(`(?:${splitSep}\\n)`, 'g');
1517
return _.chain(list)
1618
.thru(whenever(options.lowerCase, text => text.toLowerCase()))
17-
.split('\n')
19+
.split(splitRegExp)
1820
.thru(whenever(options.removeDuplicates, _.uniq))
1921
.thru(whenever(options.reverseList, _.reverse))
20-
.thru(whenever(!_.isNull(options.sortList), parts => parts.sort(byOrder({ order: options.sortList }))))
2122
.map(whenever(options.trimItems, _.trim))
23+
.thru(whenever(!!options.sortList, parts => parts.sort(byOrder({ order: options.sortList }))))
2224
.without('')
2325
.map(p => options.removeItemPrefix ? p.replace(new RegExp(`^${options.removeItemPrefix}`, 'g'), '') : p)
2426
.map(p => options.removeItemSuffix ? p.replace(new RegExp(`${options.removeItemSuffix}$`, 'g'), '') : p)
25-
.map(p => options.itemPrefix + p + options.itemSuffix)
26-
.join(options.separator + lineBreak)
27-
.thru(text => [options.listPrefix, text, options.listSuffix].join(lineBreak))
27+
.map(p => (options.itemPrefix || '') + p + (options.itemSuffix || ''))
28+
.join((options.itemsSeparator || '') + lineBreak)
29+
.thru(text => [options.listPrefix, text, options.listSuffix].filter(l => l).join(lineBreak))
2830
.value();
2931
}
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
export type SortOrder = 'asc' | 'desc' | null;
1+
export type SortOrder = null | 'asc' | 'desc' | 'asc-num' | 'desc-num' | 'asc-bin' | 'desc-bin' | 'asc-upper' | 'desc-upper';
22

33
export interface ConvertOptions {
4-
lowerCase: boolean
5-
trimItems: boolean
6-
itemPrefix: string
7-
itemSuffix: string
8-
removeItemPrefix: string
9-
removeItemSuffix: string
10-
listPrefix: string
11-
listSuffix: string
12-
reverseList: boolean
13-
sortList: SortOrder
14-
removeDuplicates: boolean
15-
separator: string
16-
keepLineBreaks: boolean
4+
lowerCase?: boolean
5+
trimItems?: boolean
6+
itemPrefix?: string
7+
itemSuffix?: string
8+
removeItemPrefix?: string
9+
removeItemSuffix?: string
10+
listPrefix?: string
11+
listSuffix?: string
12+
reverseList?: boolean
13+
sortList?: SortOrder
14+
removeDuplicates?: boolean
15+
itemsSeparator?: string
16+
splitBySeparator?: string
17+
keepLineBreaks?: boolean
1718
}

src/tools/list-converter/list-converter.vue

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,41 @@ import { convert } from './list-converter.models';
44
import type { ConvertOptions } from './list-converter.types';
55
66
const sortOrderOptions = [
7+
{
8+
label: 'No Sort',
9+
value: null,
10+
},
711
{
812
label: 'Sort ascending',
913
value: 'asc',
10-
disabled: false,
1114
},
1215
{
1316
label: 'Sort descending',
1417
value: 'desc',
15-
disabled: false,
18+
},
19+
{
20+
label: 'Sort asc (Numeric)',
21+
value: 'asc-num',
22+
},
23+
{
24+
label: 'Sort desc (Numeric)',
25+
value: 'desc-num',
26+
},
27+
{
28+
label: 'Sort asc (Upper)',
29+
value: 'asc-upper',
30+
},
31+
{
32+
label: 'Sort desc (Upper)',
33+
value: 'desc-upper',
34+
},
35+
{
36+
label: 'Sort asc (Binary)',
37+
value: 'asc-bin',
38+
},
39+
{
40+
label: 'Sort desc (Binary)',
41+
value: 'desc-bin',
1642
},
1743
];
1844
@@ -29,7 +55,8 @@ const conversionConfig = useStorage<ConvertOptions>('list-converter:conversionCo
2955
listSuffix: '',
3056
reverseList: false,
3157
sortList: null,
32-
separator: ', ',
58+
itemsSeparator: ', ',
59+
splitBySeparator: '',
3360
});
3461
3562
function transformer(value: string) {
@@ -41,7 +68,7 @@ function transformer(value: string) {
4168
<div style="flex: 0 0 100%">
4269
<div style="margin: 0 auto; max-width: 600px">
4370
<c-card>
44-
<div flex>
71+
<n-space>
4572
<div>
4673
<n-form-item label="Trim list items" label-placement="left" label-width="150" :show-feedback="false" mb-2>
4774
<n-switch v-model:value="conversionConfig.trimItems" />
@@ -62,7 +89,7 @@ function transformer(value: string) {
6289
<n-switch v-model:value="conversionConfig.keepLineBreaks" />
6390
</n-form-item>
6491
</div>
65-
<div flex-1>
92+
<div>
6693
<c-select
6794
v-model:value="conversionConfig.sortList"
6895
label="Sort list"
@@ -78,13 +105,23 @@ function transformer(value: string) {
78105
/>
79106

80107
<c-input-text
81-
v-model:value="conversionConfig.separator"
82-
label="Separator"
108+
v-model:value="conversionConfig.itemsSeparator"
109+
label="Items Separator"
110+
label-position="left"
111+
label-width="120px"
112+
label-align="right"
113+
mb-2
114+
placeholder="Items separator"
115+
/>
116+
117+
<c-input-text
118+
v-model:value="conversionConfig.splitBySeparator"
119+
label="Split Separator"
83120
label-position="left"
84121
label-width="120px"
85122
label-align="right"
86123
mb-2
87-
placeholder=","
124+
placeholder="Separator for splitting"
88125
/>
89126

90127
<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) {
125162
/>
126163
</n-form-item>
127164
</div>
128-
</div>
165+
</n-space>
129166
</c-card>
130167
</div>
131168
</div>

src/utils/array.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { type SortOrder, byOrder } from './array';
3+
4+
describe('array utils', () => {
5+
describe('byOrder', () => {
6+
it('should sort correctly', () => {
7+
const sortBy = (array: string[], order: SortOrder) => {
8+
return array.sort(byOrder({ order }));
9+
};
10+
11+
const strings = ['a', 'A', 'b', 'B', 'á', '1', '2', '10', '一', '阿'];
12+
13+
expect(sortBy(strings, null)).to.eql(strings);
14+
expect(sortBy(strings, undefined)).to.eql(strings);
15+
expect(sortBy(strings, 'asc')).to.eql(['1', '10', '2', 'a', 'A', 'á', 'b', 'B', '一', '阿']);
16+
expect(sortBy(strings, 'asc-num')).to.eql(['1', '2', '10', 'a', 'A', 'á', 'b', 'B', '一', '阿']);
17+
expect(sortBy(strings, 'asc-bin')).to.eql(['1', '10', '2', 'A', 'B', 'a', 'b', 'á', '一', '阿']);
18+
expect(sortBy(strings, 'asc-upper')).to.eql(['1', '10', '2', 'A', 'a', 'á', 'B', 'b', '一', '阿']);
19+
expect(sortBy(strings, 'desc')).to.eql(['阿', '一', 'B', 'b', 'á', 'A', 'a', '2', '10', '1']);
20+
expect(sortBy(strings, 'desc-num')).to.eql(['阿', '一', 'B', 'b', 'á', 'A', 'a', '10', '2', '1']);
21+
expect(sortBy(strings, 'desc-bin')).to.eql(['阿', '一', 'á', 'b', 'a', 'B', 'A', '2', '10', '1']);
22+
expect(sortBy(strings, 'desc-upper')).to.eql(['阿', '一', 'b', 'B', 'á', 'a', 'A', '2', '10', '1']);
23+
});
24+
});
25+
});

src/utils/array.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
1-
export { byOrder };
1+
export type SortOrder = 'asc' | 'desc' | 'asc-num' | 'desc-num' | 'asc-bin' | 'desc-bin' | 'asc-upper' | 'desc-upper' | null | undefined;
2+
3+
export function byOrder({ order }: { order: SortOrder }) {
4+
if (order === 'asc-bin' || order === 'desc-bin') {
5+
return (a: string, b: string) => {
6+
const compare = a > b ? 1 : (a < b ? -1 : 0); // NOSONAR
7+
return order === 'asc-bin' ? compare : -compare;
8+
};
9+
}
10+
if (order === 'asc-num' || order === 'desc-num') {
11+
return (a: string, b: string) => {
12+
const compare = a.localeCompare(b, undefined, {
13+
numeric: true,
14+
});
15+
return order === 'asc-num' ? compare : -compare;
16+
};
17+
}
18+
if (order === 'asc-upper' || order === 'desc-upper') {
19+
return (a: string, b: string) => {
20+
const compare = a.localeCompare(b, undefined, {
21+
caseFirst: 'upper',
22+
});
23+
return order === 'asc-upper' ? compare : -compare;
24+
};
25+
}
226

3-
function byOrder({ order }: { order: 'asc' | 'desc' | null | undefined }) {
427
return (a: string, b: string) => {
528
return order === 'asc' ? a.localeCompare(b) : b.localeCompare(a);
629
};

0 commit comments

Comments
 (0)