@etsoo/shared 1.2.79 → 1.2.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,10 @@ on:
11
11
  # release:
12
12
  # types: [created]
13
13
 
14
+ permissions:
15
+ id-token: write # Required for OIDC
16
+ contents: read
17
+
14
18
  jobs:
15
19
  # Publish to NPM
16
20
  publish-npm:
@@ -44,6 +48,4 @@ jobs:
44
48
 
45
49
  # Publish to npm
46
50
  # For scoped package, make it public for free service
47
- - run: npm publish --access public
48
- env:
49
- NODE_AUTH_TOKEN: ${{ secrets.ETSOONpmToken }}
51
+ - run: npm publish
package/README.md CHANGED
@@ -324,6 +324,7 @@ String and other related utilities
324
324
  | objectKeys | Get two object's unqiue properties |
325
325
  | objectUpdated | Get the new object's updated fields contrast to the previous object |
326
326
  | parseJsonArray | Try to parse JSON input to array |
327
+ | parseName | Parse name data |
327
328
  | parsePath | Parse path similar with node.js path.parse |
328
329
  | parseString | Parse string (JSON) to specific type |
329
330
  | removeEmptyValues | Remove empty values (null, undefined, '') from the input object |
@@ -328,6 +328,16 @@ test("Tests for mergeClasses", () => {
328
328
  expect(Utils.mergeClasses("a", "", "b ", undefined, "c")).toBe("a b c");
329
329
  });
330
330
 
331
+ test("Tests for formatName", () => {
332
+ expect(Utils.formatName("青岛亿速思维网络科技有限公司", 6)).toBe(
333
+ "青岛亿速思维"
334
+ );
335
+ expect(Utils.formatName("亿速思维(中国)网络科技有限公司", 6)).toBe(
336
+ "亿速思维(中国)"
337
+ );
338
+ expect(Utils.formatName("John Smith", 6)).toBe("John Smith");
339
+ });
340
+
331
341
  test("Tests for getNestedValue", () => {
332
342
  const obj = { jsonData: { photoSize: [200, 100], supportResizing: true } };
333
343
  expect(Utils.getNestedValue(obj, "jsonData.supportResizing")).toBeTruthy();
@@ -356,6 +366,20 @@ test("Tests for getResult", () => {
356
366
  expect(valueResult).toBe(5);
357
367
  });
358
368
 
369
+ test("Tests for parseName", () => {
370
+ const nd = Utils.parseName("王小明");
371
+ expect(nd.familyName).toBe("王");
372
+ expect(nd.givenName).toBe("小明");
373
+
374
+ const nd2 = Utils.parseName("John Smith");
375
+ expect(nd2.familyName).toBe("Smith");
376
+ expect(nd2.givenName).toBe("John");
377
+
378
+ const nd3 = Utils.parseName("单 名");
379
+ expect(nd3.familyName).toBe("单");
380
+ expect(nd3.givenName).toBe("名");
381
+ });
382
+
359
383
  test("Tests for parsePath, file only", () => {
360
384
  const result = Utils.parsePath("a.jpg");
361
385
  expect(result.root).toBe("");
@@ -132,6 +132,14 @@ export declare namespace Utils {
132
132
  export function excludeAsync<T extends {
133
133
  [P in D]: IdType;
134
134
  }, D extends string = "id">(items: Promise<T[] | undefined>, field: D, ...excludedValues: T[D][]): Promise<T[] | undefined>;
135
+ /**
136
+ * Format name
137
+ * @param name Input name
138
+ * @param maxChars Max chars
139
+ * @param maxParts Max parts (optional)
140
+ * @returns Formatted name
141
+ */
142
+ export function formatName(name: string, maxChars: number, maxParts?: number): string;
135
143
  /**
136
144
  * Format inital character to lower case or upper case
137
145
  * @param input Input string
@@ -306,6 +314,15 @@ export declare namespace Utils {
306
314
  * @param keepNull Keep null value or not
307
315
  */
308
316
  export function setNestedValue(data: object, name: string, value: unknown, keepNull?: boolean): void;
317
+ /**
318
+ * Parse name
319
+ * @param name Name
320
+ * @returns Result
321
+ */
322
+ export function parseName(name: string): {
323
+ familyName: string | undefined;
324
+ givenName: string | undefined;
325
+ };
309
326
  /**
310
327
  * Parse path similar with node.js path.parse
311
328
  * @param path Input path
package/lib/cjs/Utils.js CHANGED
@@ -207,6 +207,63 @@ var Utils;
207
207
  return exclude(result, field, ...excludedValues);
208
208
  }
209
209
  Utils.excludeAsync = excludeAsync;
210
+ /**
211
+ * Format name
212
+ * @param name Input name
213
+ * @param maxChars Max chars
214
+ * @param maxParts Max parts (optional)
215
+ * @returns Formatted name
216
+ */
217
+ function formatName(name, maxChars, maxParts) {
218
+ name = name.trim();
219
+ const parts = name.split(/\s+/);
220
+ const max = maxParts ?? Math.floor(maxChars / 3);
221
+ const effectiveMax = max < 2 ? 2 : max;
222
+ if (parts.length >= effectiveMax) {
223
+ return parts.slice(0, effectiveMax).join(" ");
224
+ }
225
+ else if (name.length > maxChars) {
226
+ let endIndex = maxChars;
227
+ const brackets = {
228
+ "(": ")",
229
+ "(": ")",
230
+ "[": "]"
231
+ };
232
+ // Count unmatched brackets for each type
233
+ for (const [start, end] of Object.entries(brackets)) {
234
+ let count = 0;
235
+ // Count opening and closing brackets in the substring
236
+ for (let i = 0; i < maxChars; i++) {
237
+ if (name[i] === start)
238
+ count++;
239
+ else if (name[i] === end)
240
+ count--;
241
+ }
242
+ if (count > 0) {
243
+ // Find matching end brackets
244
+ for (let i = maxChars; i < name.length && count > 0; i++) {
245
+ if (name[i] === start) {
246
+ count++;
247
+ }
248
+ else if (name[i] === end) {
249
+ count--;
250
+ if (count === 0) {
251
+ endIndex = i + 1;
252
+ }
253
+ }
254
+ }
255
+ if (count === 0) {
256
+ return name.substring(0, endIndex);
257
+ }
258
+ }
259
+ }
260
+ return name.substring(0, endIndex);
261
+ }
262
+ else {
263
+ return name;
264
+ }
265
+ }
266
+ Utils.formatName = formatName;
210
267
  /**
211
268
  * Format inital character to lower case or upper case
212
269
  * @param input Input string
@@ -578,7 +635,7 @@ var Utils;
578
635
  Utils.setLabels = (source, labels, reference) => {
579
636
  Object.keys(source).forEach((key) => {
580
637
  // Reference key
581
- const labelKey = reference == null ? key : reference[key] ?? key;
638
+ const labelKey = reference == null ? key : (reference[key] ?? key);
582
639
  // Label
583
640
  const label = labels[labelKey];
584
641
  if (label != null) {
@@ -647,6 +704,42 @@ var Utils;
647
704
  }
648
705
  }
649
706
  Utils.setNestedValue = setNestedValue;
707
+ /**
708
+ * Parse name
709
+ * @param name Name
710
+ * @returns Result
711
+ */
712
+ function parseName(name) {
713
+ // Trim
714
+ name = name.trim();
715
+ let familyName;
716
+ let givenName;
717
+ if (name.containChinese() ||
718
+ name.containJapanese() ||
719
+ name.containKorean()) {
720
+ // CJK characters
721
+ if (name.length >= 2) {
722
+ familyName = name[0];
723
+ givenName = name.substring(1).trim();
724
+ }
725
+ else {
726
+ familyName = name;
727
+ }
728
+ }
729
+ else {
730
+ const parts = name.split(/\s+/);
731
+ const len = parts.length;
732
+ if (len >= 2) {
733
+ familyName = parts[len - 1];
734
+ givenName = parts[0];
735
+ }
736
+ else {
737
+ givenName = name;
738
+ }
739
+ }
740
+ return { familyName, givenName };
741
+ }
742
+ Utils.parseName = parseName;
650
743
  /**
651
744
  * Parse path similar with node.js path.parse
652
745
  * @param path Input path
@@ -63,5 +63,5 @@ export interface IStorage {
63
63
  * @param key Key name
64
64
  * @param data Data, null for removal
65
65
  */
66
- setPersistedData(key: string, data: unknown): void;
66
+ setPersistedData<T>(key: string, data: T): void;
67
67
  }
@@ -65,5 +65,5 @@ export declare class WindowStorage implements IStorage {
65
65
  * @param key Key name
66
66
  * @param data Data, null for removal
67
67
  */
68
- setPersistedData(key: string, data: unknown): void;
68
+ setPersistedData<T>(key: string, data: T): void;
69
69
  }
@@ -132,6 +132,14 @@ export declare namespace Utils {
132
132
  export function excludeAsync<T extends {
133
133
  [P in D]: IdType;
134
134
  }, D extends string = "id">(items: Promise<T[] | undefined>, field: D, ...excludedValues: T[D][]): Promise<T[] | undefined>;
135
+ /**
136
+ * Format name
137
+ * @param name Input name
138
+ * @param maxChars Max chars
139
+ * @param maxParts Max parts (optional)
140
+ * @returns Formatted name
141
+ */
142
+ export function formatName(name: string, maxChars: number, maxParts?: number): string;
135
143
  /**
136
144
  * Format inital character to lower case or upper case
137
145
  * @param input Input string
@@ -306,6 +314,15 @@ export declare namespace Utils {
306
314
  * @param keepNull Keep null value or not
307
315
  */
308
316
  export function setNestedValue(data: object, name: string, value: unknown, keepNull?: boolean): void;
317
+ /**
318
+ * Parse name
319
+ * @param name Name
320
+ * @returns Result
321
+ */
322
+ export function parseName(name: string): {
323
+ familyName: string | undefined;
324
+ givenName: string | undefined;
325
+ };
309
326
  /**
310
327
  * Parse path similar with node.js path.parse
311
328
  * @param path Input path
package/lib/mjs/Utils.js CHANGED
@@ -204,6 +204,63 @@ export var Utils;
204
204
  return exclude(result, field, ...excludedValues);
205
205
  }
206
206
  Utils.excludeAsync = excludeAsync;
207
+ /**
208
+ * Format name
209
+ * @param name Input name
210
+ * @param maxChars Max chars
211
+ * @param maxParts Max parts (optional)
212
+ * @returns Formatted name
213
+ */
214
+ function formatName(name, maxChars, maxParts) {
215
+ name = name.trim();
216
+ const parts = name.split(/\s+/);
217
+ const max = maxParts ?? Math.floor(maxChars / 3);
218
+ const effectiveMax = max < 2 ? 2 : max;
219
+ if (parts.length >= effectiveMax) {
220
+ return parts.slice(0, effectiveMax).join(" ");
221
+ }
222
+ else if (name.length > maxChars) {
223
+ let endIndex = maxChars;
224
+ const brackets = {
225
+ "(": ")",
226
+ "(": ")",
227
+ "[": "]"
228
+ };
229
+ // Count unmatched brackets for each type
230
+ for (const [start, end] of Object.entries(brackets)) {
231
+ let count = 0;
232
+ // Count opening and closing brackets in the substring
233
+ for (let i = 0; i < maxChars; i++) {
234
+ if (name[i] === start)
235
+ count++;
236
+ else if (name[i] === end)
237
+ count--;
238
+ }
239
+ if (count > 0) {
240
+ // Find matching end brackets
241
+ for (let i = maxChars; i < name.length && count > 0; i++) {
242
+ if (name[i] === start) {
243
+ count++;
244
+ }
245
+ else if (name[i] === end) {
246
+ count--;
247
+ if (count === 0) {
248
+ endIndex = i + 1;
249
+ }
250
+ }
251
+ }
252
+ if (count === 0) {
253
+ return name.substring(0, endIndex);
254
+ }
255
+ }
256
+ }
257
+ return name.substring(0, endIndex);
258
+ }
259
+ else {
260
+ return name;
261
+ }
262
+ }
263
+ Utils.formatName = formatName;
207
264
  /**
208
265
  * Format inital character to lower case or upper case
209
266
  * @param input Input string
@@ -575,7 +632,7 @@ export var Utils;
575
632
  Utils.setLabels = (source, labels, reference) => {
576
633
  Object.keys(source).forEach((key) => {
577
634
  // Reference key
578
- const labelKey = reference == null ? key : reference[key] ?? key;
635
+ const labelKey = reference == null ? key : (reference[key] ?? key);
579
636
  // Label
580
637
  const label = labels[labelKey];
581
638
  if (label != null) {
@@ -644,6 +701,42 @@ export var Utils;
644
701
  }
645
702
  }
646
703
  Utils.setNestedValue = setNestedValue;
704
+ /**
705
+ * Parse name
706
+ * @param name Name
707
+ * @returns Result
708
+ */
709
+ function parseName(name) {
710
+ // Trim
711
+ name = name.trim();
712
+ let familyName;
713
+ let givenName;
714
+ if (name.containChinese() ||
715
+ name.containJapanese() ||
716
+ name.containKorean()) {
717
+ // CJK characters
718
+ if (name.length >= 2) {
719
+ familyName = name[0];
720
+ givenName = name.substring(1).trim();
721
+ }
722
+ else {
723
+ familyName = name;
724
+ }
725
+ }
726
+ else {
727
+ const parts = name.split(/\s+/);
728
+ const len = parts.length;
729
+ if (len >= 2) {
730
+ familyName = parts[len - 1];
731
+ givenName = parts[0];
732
+ }
733
+ else {
734
+ givenName = name;
735
+ }
736
+ }
737
+ return { familyName, givenName };
738
+ }
739
+ Utils.parseName = parseName;
647
740
  /**
648
741
  * Parse path similar with node.js path.parse
649
742
  * @param path Input path
@@ -63,5 +63,5 @@ export interface IStorage {
63
63
  * @param key Key name
64
64
  * @param data Data, null for removal
65
65
  */
66
- setPersistedData(key: string, data: unknown): void;
66
+ setPersistedData<T>(key: string, data: T): void;
67
67
  }
@@ -65,5 +65,5 @@ export declare class WindowStorage implements IStorage {
65
65
  * @param key Key name
66
66
  * @param data Data, null for removal
67
67
  */
68
- setPersistedData(key: string, data: unknown): void;
68
+ setPersistedData<T>(key: string, data: T): void;
69
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/shared",
3
- "version": "1.2.79",
3
+ "version": "1.2.81",
4
4
  "description": "TypeScript shared utilities and functions",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -36,10 +36,10 @@
36
36
  },
37
37
  "homepage": "https://github.com/ETSOO/Shared#readme",
38
38
  "devDependencies": {
39
- "@types/node": "^24.10.0",
40
- "@vitejs/plugin-react": "^5.1.0",
41
- "jsdom": "^27.1.0",
39
+ "@types/node": "^25.5.0",
40
+ "@vitejs/plugin-react": "^6.0.1",
41
+ "jsdom": "^29.0.1",
42
42
  "typescript": "^5.9.3",
43
- "vitest": "^4.0.7"
43
+ "vitest": "^4.1.2"
44
44
  }
45
45
  }
package/src/Utils.ts CHANGED
@@ -355,6 +355,70 @@ export namespace Utils {
355
355
  return exclude(result, field, ...excludedValues);
356
356
  }
357
357
 
358
+ /**
359
+ * Format name
360
+ * @param name Input name
361
+ * @param maxChars Max chars
362
+ * @param maxParts Max parts (optional)
363
+ * @returns Formatted name
364
+ */
365
+ export function formatName(
366
+ name: string,
367
+ maxChars: number,
368
+ maxParts?: number
369
+ ): string {
370
+ name = name.trim();
371
+
372
+ const parts = name.split(/\s+/);
373
+
374
+ const max = maxParts ?? Math.floor(maxChars / 3);
375
+ const effectiveMax = max < 2 ? 2 : max;
376
+
377
+ if (parts.length >= effectiveMax) {
378
+ return parts.slice(0, effectiveMax).join(" ");
379
+ } else if (name.length > maxChars) {
380
+ let endIndex = maxChars;
381
+ const brackets: Record<string, string> = {
382
+ "(": ")",
383
+ "(": ")",
384
+ "[": "]"
385
+ };
386
+
387
+ // Count unmatched brackets for each type
388
+ for (const [start, end] of Object.entries(brackets)) {
389
+ let count = 0;
390
+
391
+ // Count opening and closing brackets in the substring
392
+ for (let i = 0; i < maxChars; i++) {
393
+ if (name[i] === start) count++;
394
+ else if (name[i] === end) count--;
395
+ }
396
+
397
+ if (count > 0) {
398
+ // Find matching end brackets
399
+ for (let i = maxChars; i < name.length && count > 0; i++) {
400
+ if (name[i] === start) {
401
+ count++;
402
+ } else if (name[i] === end) {
403
+ count--;
404
+ if (count === 0) {
405
+ endIndex = i + 1;
406
+ }
407
+ }
408
+ }
409
+
410
+ if (count === 0) {
411
+ return name.substring(0, endIndex);
412
+ }
413
+ }
414
+ }
415
+
416
+ return name.substring(0, endIndex);
417
+ } else {
418
+ return name;
419
+ }
420
+ }
421
+
358
422
  /**
359
423
  * Format inital character to lower case or upper case
360
424
  * @param input Input string
@@ -823,7 +887,7 @@ export namespace Utils {
823
887
  ) => {
824
888
  Object.keys(source).forEach((key) => {
825
889
  // Reference key
826
- const labelKey = reference == null ? key : reference[key] ?? key;
890
+ const labelKey = reference == null ? key : (reference[key] ?? key);
827
891
 
828
892
  // Label
829
893
  const label = labels[labelKey];
@@ -899,6 +963,44 @@ export namespace Utils {
899
963
  }
900
964
  }
901
965
 
966
+ /**
967
+ * Parse name
968
+ * @param name Name
969
+ * @returns Result
970
+ */
971
+ export function parseName(name: string) {
972
+ // Trim
973
+ name = name.trim();
974
+
975
+ let familyName: string | undefined;
976
+ let givenName: string | undefined;
977
+
978
+ if (
979
+ name.containChinese() ||
980
+ name.containJapanese() ||
981
+ name.containKorean()
982
+ ) {
983
+ // CJK characters
984
+ if (name.length >= 2) {
985
+ familyName = name[0];
986
+ givenName = name.substring(1).trim();
987
+ } else {
988
+ familyName = name;
989
+ }
990
+ } else {
991
+ const parts = name.split(/\s+/);
992
+ const len = parts.length;
993
+ if (len >= 2) {
994
+ familyName = parts[len - 1];
995
+ givenName = parts[0];
996
+ } else {
997
+ givenName = name;
998
+ }
999
+ }
1000
+
1001
+ return { familyName, givenName };
1002
+ }
1003
+
902
1004
  /**
903
1005
  * Parse path similar with node.js path.parse
904
1006
  * @param path Input path
@@ -925,8 +1027,8 @@ export namespace Utils {
925
1027
  index1 === -1
926
1028
  ? index2
927
1029
  : index2 === -1
928
- ? index1
929
- : Math.min(index1, index2);
1030
+ ? index1
1031
+ : Math.min(index1, index2);
930
1032
  root = path.substring(0, index + 1);
931
1033
  dir = path.substring(0, lastIndex);
932
1034
  if (dir === "") dir = root;
@@ -73,5 +73,5 @@ export interface IStorage {
73
73
  * @param key Key name
74
74
  * @param data Data, null for removal
75
75
  */
76
- setPersistedData(key: string, data: unknown): void;
76
+ setPersistedData<T>(key: string, data: T): void;
77
77
  }
@@ -141,7 +141,7 @@ export class WindowStorage implements IStorage {
141
141
  * @param key Key name
142
142
  * @param data Data, null for removal
143
143
  */
144
- setPersistedData(key: string, data: unknown) {
144
+ setPersistedData<T>(key: string, data: T) {
145
145
  StorageUtils.setLocalData(key, data);
146
146
  }
147
147
  }