mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-04-23 00:06:15 -04:00
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
7 changed files with 168 additions and 72 deletions
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…
Add table
Add a link
Reference in a new issue