@etsoo/appscript 1.4.96 → 1.4.98

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.
@@ -264,6 +264,30 @@ test('Tests for formatResult', () => {
264
264
  );
265
265
  });
266
266
 
267
+ test('Tests for formatResult with custom label', () => {
268
+ const result: IActionResult = {
269
+ ok: false,
270
+ type: 'ItemRequired',
271
+ title: '{0} is required.',
272
+ field: 'url'
273
+ };
274
+
275
+ const formatted = app.formatResult(result, () => 'Url');
276
+ expect(formatted).toBe('Url is required. (ItemRequired, url)');
277
+ });
278
+
279
+ test('Tests for formatResult with custom label', () => {
280
+ const result: IActionResult = {
281
+ ok: false,
282
+ type: 'ItemExists',
283
+ field: 'url'
284
+ };
285
+
286
+ const fieldLabel = '文章地址';
287
+ const formatted = app.formatResult(result, () => fieldLabel);
288
+ expect(formatted).toBe(`'${fieldLabel}'已经存在 (ItemExists, url)`);
289
+ });
290
+
267
291
  test('Tests for formatError', () => {
268
292
  const error: ApiDataError = {
269
293
  name: 'ApiDataError',
@@ -7,7 +7,7 @@ import { InitCallDto } from '../erp/dto/InitCallDto';
7
7
  import { InitCallResult, InitCallResultData } from '../result/InitCallResult';
8
8
  import { IUser } from '../state/User';
9
9
  import { IAppSettings } from './AppSettings';
10
- import { IApp, IAppFields, IDetectIPCallback, NavigateOptions, RefreshTokenProps, RefreshTokenResult } from './IApp';
10
+ import { FormatResultCustomCallback, IApp, IAppFields, IDetectIPCallback, NavigateOptions, RefreshTokenProps, RefreshTokenResult } from './IApp';
11
11
  import { UserRole } from './UserRole';
12
12
  /**
13
13
  * Core application interface
@@ -229,12 +229,19 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
229
229
  * @returns Fields
230
230
  */
231
231
  protected initCallEncryptedUpdateFields(): string[];
232
+ /**
233
+ * Alert result
234
+ * @param result Result message
235
+ * @param callback Callback
236
+ */
237
+ alertResult(result: string, callback?: NotificationReturn<void>): void;
232
238
  /**
233
239
  * Alert action result
234
- * @param result Action result or message
240
+ * @param result Action result
235
241
  * @param callback Callback
242
+ * @param forceToLocal Force to local labels
236
243
  */
237
- alertResult(result: IActionResult | string, callback?: NotificationReturn<void>): void;
244
+ alertResult(result: IActionResult, callback?: NotificationReturn<void>, forceToLocal?: FormatResultCustomCallback): void;
238
245
  /**
239
246
  * Authorize
240
247
  * @param token New token
@@ -381,12 +388,13 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
381
388
  * @returns Message
382
389
  */
383
390
  formatRefreshTokenResult(result: RefreshTokenResult): string | undefined;
391
+ private getFieldLabel;
384
392
  /**
385
393
  * Format result text
386
394
  * @param result Action result
387
395
  * @param forceToLocal Force to local labels
388
396
  */
389
- formatResult(result: IActionResult, forceToLocal?: boolean): string;
397
+ formatResult(result: IActionResult, forceToLocal?: FormatResultCustomCallback): string;
390
398
  /**
391
399
  * Get culture resource
392
400
  * @param key key
@@ -510,6 +518,12 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
510
518
  * @param options Options
511
519
  */
512
520
  navigate<T extends number | string | URL>(to: T, options?: T extends number ? never : NavigateOptions): void;
521
+ /**
522
+ * Notify user with success message
523
+ * @param callback Popup close callback
524
+ * @param message Success message
525
+ */
526
+ ok(callback?: NotificationReturn<void>, message?: NotificationContent<N>): void;
513
527
  /**
514
528
  * Callback where exit a page
515
529
  */
@@ -504,13 +504,10 @@ class CoreApp {
504
504
  initCallEncryptedUpdateFields() {
505
505
  return [this.fields.headerToken];
506
506
  }
507
- /**
508
- * Alert action result
509
- * @param result Action result or message
510
- * @param callback Callback
511
- */
512
- alertResult(result, callback) {
513
- const message = typeof result === 'string' ? result : this.formatResult(result);
507
+ alertResult(result, callback, forceToLocal) {
508
+ const message = typeof result === 'string'
509
+ ? result
510
+ : this.formatResult(result, forceToLocal);
514
511
  this.notifier.alert(message, callback);
515
512
  }
516
513
  /**
@@ -939,33 +936,47 @@ class CoreApp {
939
936
  ? ActionResultError_1.ActionResultError.format(result)
940
937
  : result;
941
938
  }
939
+ getFieldLabel(field) {
940
+ return this.get(field.formatInitial(false)) ?? field;
941
+ }
942
942
  /**
943
943
  * Format result text
944
944
  * @param result Action result
945
945
  * @param forceToLocal Force to local labels
946
946
  */
947
947
  formatResult(result, forceToLocal) {
948
- const title = result.title;
949
- if (title && /^\w+$/.test(title)) {
950
- const key = title.formatInitial(false);
951
- const localTitle = this.get(key);
952
- if (localTitle) {
953
- result.title = localTitle;
954
- // Hold the original title in type when type is null
955
- if (result.type == null)
956
- result.type = title;
957
- }
948
+ // Destruct the result
949
+ const { title, type, field } = result;
950
+ const data = { title, type, field };
951
+ if (type === 'ItemExists' && field) {
952
+ // Special case
953
+ const fieldLabel = (typeof forceToLocal === 'function'
954
+ ? forceToLocal(data)
955
+ : undefined) ?? this.getFieldLabel(field);
956
+ result.title = this.get('itemExists')?.format(fieldLabel);
958
957
  }
959
- else if ((title == null || forceToLocal) && result.type != null) {
960
- // Get label from type
961
- const key = result.type.formatInitial(false);
962
- result.title = this.get(key);
958
+ else if (title?.includes('{0}')) {
959
+ // When title contains {0}, replace with the field label
960
+ const fieldLabel = (typeof forceToLocal === 'function'
961
+ ? forceToLocal(data)
962
+ : undefined) ?? (field ? this.getFieldLabel(field) : '');
963
+ result.title = title.format(fieldLabel);
963
964
  }
964
- // When title contains {0}, replace with the field label
965
- if (result.field && result.title?.includes('{0}')) {
966
- const fieldLabel = this.get(result.field.formatInitial(false));
967
- if (fieldLabel)
968
- result.title = result.title.format(fieldLabel);
965
+ else if (title && /^\w+$/.test(title)) {
966
+ // When title is a single word
967
+ // Hold the original title in type when type is null
968
+ if (type == null)
969
+ result.type = title;
970
+ const localTitle = (typeof forceToLocal === 'function'
971
+ ? forceToLocal(data)
972
+ : undefined) ?? this.getFieldLabel(title);
973
+ result.title = localTitle;
974
+ }
975
+ else if ((title == null || forceToLocal) && type != null) {
976
+ const localTitle = (typeof forceToLocal === 'function'
977
+ ? forceToLocal(data)
978
+ : undefined) ?? this.getFieldLabel(type);
979
+ result.title = localTitle;
969
980
  }
970
981
  return ActionResultError_1.ActionResultError.format(result);
971
982
  }
@@ -1237,6 +1248,15 @@ class CoreApp {
1237
1248
  }
1238
1249
  }
1239
1250
  }
1251
+ /**
1252
+ * Notify user with success message
1253
+ * @param callback Popup close callback
1254
+ * @param message Success message
1255
+ */
1256
+ ok(callback, message) {
1257
+ message ?? (message = this.get('operationSucceeded'));
1258
+ this.notifier.succeed(message, undefined, callback);
1259
+ }
1240
1260
  /**
1241
1261
  * Callback where exit a page
1242
1262
  */
@@ -26,6 +26,18 @@ export interface NavigateOptions {
26
26
  * other cases means failed with differnet message
27
27
  */
28
28
  export type RefreshTokenResult = boolean | string | ApiDataError | IActionResult;
29
+ /**
30
+ * Format result custom type
31
+ */
32
+ export type FormatResultCustom = {
33
+ title?: string;
34
+ type?: string;
35
+ field?: string;
36
+ };
37
+ /**
38
+ * Format result custom callback type
39
+ */
40
+ export type FormatResultCustomCallback = ((data: FormatResultCustom) => string | null | undefined) | boolean;
29
41
  /**
30
42
  * Refresh token props
31
43
  */
@@ -147,12 +159,19 @@ export interface IApp {
147
159
  * @returns Result
148
160
  */
149
161
  addRootUrl(url: string): string;
162
+ /**
163
+ * Alert result
164
+ * @param result Result message
165
+ * @param callback Callback
166
+ */
167
+ alertResult(result: string, callback?: NotificationReturn<void>): void;
150
168
  /**
151
169
  * Alert action result
152
- * @param result Action result or message
170
+ * @param result Action result
153
171
  * @param callback Callback
172
+ * @param forceToLocal Force to local labels
154
173
  */
155
- alertResult(result: IActionResult | string, callback?: NotificationReturn<void>): void;
174
+ alertResult(result: IActionResult, callback?: NotificationReturn<void>, forceToLocal?: FormatResultCustomCallback): void;
156
175
  /**
157
176
  * Authorize
158
177
  * @param token New token
@@ -298,7 +317,7 @@ export interface IApp {
298
317
  * @param forceToLocal Force to local labels
299
318
  * @returns Message
300
319
  */
301
- formatResult(result: IActionResult, forceToLocal?: boolean): string;
320
+ formatResult(result: IActionResult, forceToLocal?: FormatResultCustomCallback): string;
302
321
  /**
303
322
  * Fresh countdown UI
304
323
  * @param callback Callback
@@ -439,6 +458,12 @@ export interface IApp {
439
458
  * @param options Options
440
459
  */
441
460
  navigate<T extends number | string | URL>(to: T, options?: T extends number ? never : NavigateOptions): void;
461
+ /**
462
+ * Notify user with success message
463
+ * @param message Success message
464
+ * @param callback Popup close callback
465
+ */
466
+ ok(callback?: NotificationReturn<void>, message?: NotificationContent<unknown>): void;
442
467
  /**
443
468
  * Callback where exit a page
444
469
  */
@@ -7,7 +7,7 @@ import { InitCallDto } from '../erp/dto/InitCallDto';
7
7
  import { InitCallResult, InitCallResultData } from '../result/InitCallResult';
8
8
  import { IUser } from '../state/User';
9
9
  import { IAppSettings } from './AppSettings';
10
- import { IApp, IAppFields, IDetectIPCallback, NavigateOptions, RefreshTokenProps, RefreshTokenResult } from './IApp';
10
+ import { FormatResultCustomCallback, IApp, IAppFields, IDetectIPCallback, NavigateOptions, RefreshTokenProps, RefreshTokenResult } from './IApp';
11
11
  import { UserRole } from './UserRole';
12
12
  /**
13
13
  * Core application interface
@@ -229,12 +229,19 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
229
229
  * @returns Fields
230
230
  */
231
231
  protected initCallEncryptedUpdateFields(): string[];
232
+ /**
233
+ * Alert result
234
+ * @param result Result message
235
+ * @param callback Callback
236
+ */
237
+ alertResult(result: string, callback?: NotificationReturn<void>): void;
232
238
  /**
233
239
  * Alert action result
234
- * @param result Action result or message
240
+ * @param result Action result
235
241
  * @param callback Callback
242
+ * @param forceToLocal Force to local labels
236
243
  */
237
- alertResult(result: IActionResult | string, callback?: NotificationReturn<void>): void;
244
+ alertResult(result: IActionResult, callback?: NotificationReturn<void>, forceToLocal?: FormatResultCustomCallback): void;
238
245
  /**
239
246
  * Authorize
240
247
  * @param token New token
@@ -381,12 +388,13 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
381
388
  * @returns Message
382
389
  */
383
390
  formatRefreshTokenResult(result: RefreshTokenResult): string | undefined;
391
+ private getFieldLabel;
384
392
  /**
385
393
  * Format result text
386
394
  * @param result Action result
387
395
  * @param forceToLocal Force to local labels
388
396
  */
389
- formatResult(result: IActionResult, forceToLocal?: boolean): string;
397
+ formatResult(result: IActionResult, forceToLocal?: FormatResultCustomCallback): string;
390
398
  /**
391
399
  * Get culture resource
392
400
  * @param key key
@@ -510,6 +518,12 @@ export declare abstract class CoreApp<U extends IUser, S extends IAppSettings, N
510
518
  * @param options Options
511
519
  */
512
520
  navigate<T extends number | string | URL>(to: T, options?: T extends number ? never : NavigateOptions): void;
521
+ /**
522
+ * Notify user with success message
523
+ * @param callback Popup close callback
524
+ * @param message Success message
525
+ */
526
+ ok(callback?: NotificationReturn<void>, message?: NotificationContent<N>): void;
513
527
  /**
514
528
  * Callback where exit a page
515
529
  */
@@ -501,13 +501,10 @@ export class CoreApp {
501
501
  initCallEncryptedUpdateFields() {
502
502
  return [this.fields.headerToken];
503
503
  }
504
- /**
505
- * Alert action result
506
- * @param result Action result or message
507
- * @param callback Callback
508
- */
509
- alertResult(result, callback) {
510
- const message = typeof result === 'string' ? result : this.formatResult(result);
504
+ alertResult(result, callback, forceToLocal) {
505
+ const message = typeof result === 'string'
506
+ ? result
507
+ : this.formatResult(result, forceToLocal);
511
508
  this.notifier.alert(message, callback);
512
509
  }
513
510
  /**
@@ -936,33 +933,47 @@ export class CoreApp {
936
933
  ? ActionResultError.format(result)
937
934
  : result;
938
935
  }
936
+ getFieldLabel(field) {
937
+ return this.get(field.formatInitial(false)) ?? field;
938
+ }
939
939
  /**
940
940
  * Format result text
941
941
  * @param result Action result
942
942
  * @param forceToLocal Force to local labels
943
943
  */
944
944
  formatResult(result, forceToLocal) {
945
- const title = result.title;
946
- if (title && /^\w+$/.test(title)) {
947
- const key = title.formatInitial(false);
948
- const localTitle = this.get(key);
949
- if (localTitle) {
950
- result.title = localTitle;
951
- // Hold the original title in type when type is null
952
- if (result.type == null)
953
- result.type = title;
954
- }
945
+ // Destruct the result
946
+ const { title, type, field } = result;
947
+ const data = { title, type, field };
948
+ if (type === 'ItemExists' && field) {
949
+ // Special case
950
+ const fieldLabel = (typeof forceToLocal === 'function'
951
+ ? forceToLocal(data)
952
+ : undefined) ?? this.getFieldLabel(field);
953
+ result.title = this.get('itemExists')?.format(fieldLabel);
955
954
  }
956
- else if ((title == null || forceToLocal) && result.type != null) {
957
- // Get label from type
958
- const key = result.type.formatInitial(false);
959
- result.title = this.get(key);
955
+ else if (title?.includes('{0}')) {
956
+ // When title contains {0}, replace with the field label
957
+ const fieldLabel = (typeof forceToLocal === 'function'
958
+ ? forceToLocal(data)
959
+ : undefined) ?? (field ? this.getFieldLabel(field) : '');
960
+ result.title = title.format(fieldLabel);
960
961
  }
961
- // When title contains {0}, replace with the field label
962
- if (result.field && result.title?.includes('{0}')) {
963
- const fieldLabel = this.get(result.field.formatInitial(false));
964
- if (fieldLabel)
965
- result.title = result.title.format(fieldLabel);
962
+ else if (title && /^\w+$/.test(title)) {
963
+ // When title is a single word
964
+ // Hold the original title in type when type is null
965
+ if (type == null)
966
+ result.type = title;
967
+ const localTitle = (typeof forceToLocal === 'function'
968
+ ? forceToLocal(data)
969
+ : undefined) ?? this.getFieldLabel(title);
970
+ result.title = localTitle;
971
+ }
972
+ else if ((title == null || forceToLocal) && type != null) {
973
+ const localTitle = (typeof forceToLocal === 'function'
974
+ ? forceToLocal(data)
975
+ : undefined) ?? this.getFieldLabel(type);
976
+ result.title = localTitle;
966
977
  }
967
978
  return ActionResultError.format(result);
968
979
  }
@@ -1234,6 +1245,15 @@ export class CoreApp {
1234
1245
  }
1235
1246
  }
1236
1247
  }
1248
+ /**
1249
+ * Notify user with success message
1250
+ * @param callback Popup close callback
1251
+ * @param message Success message
1252
+ */
1253
+ ok(callback, message) {
1254
+ message ?? (message = this.get('operationSucceeded'));
1255
+ this.notifier.succeed(message, undefined, callback);
1256
+ }
1237
1257
  /**
1238
1258
  * Callback where exit a page
1239
1259
  */
@@ -26,6 +26,18 @@ export interface NavigateOptions {
26
26
  * other cases means failed with differnet message
27
27
  */
28
28
  export type RefreshTokenResult = boolean | string | ApiDataError | IActionResult;
29
+ /**
30
+ * Format result custom type
31
+ */
32
+ export type FormatResultCustom = {
33
+ title?: string;
34
+ type?: string;
35
+ field?: string;
36
+ };
37
+ /**
38
+ * Format result custom callback type
39
+ */
40
+ export type FormatResultCustomCallback = ((data: FormatResultCustom) => string | null | undefined) | boolean;
29
41
  /**
30
42
  * Refresh token props
31
43
  */
@@ -147,12 +159,19 @@ export interface IApp {
147
159
  * @returns Result
148
160
  */
149
161
  addRootUrl(url: string): string;
162
+ /**
163
+ * Alert result
164
+ * @param result Result message
165
+ * @param callback Callback
166
+ */
167
+ alertResult(result: string, callback?: NotificationReturn<void>): void;
150
168
  /**
151
169
  * Alert action result
152
- * @param result Action result or message
170
+ * @param result Action result
153
171
  * @param callback Callback
172
+ * @param forceToLocal Force to local labels
154
173
  */
155
- alertResult(result: IActionResult | string, callback?: NotificationReturn<void>): void;
174
+ alertResult(result: IActionResult, callback?: NotificationReturn<void>, forceToLocal?: FormatResultCustomCallback): void;
156
175
  /**
157
176
  * Authorize
158
177
  * @param token New token
@@ -298,7 +317,7 @@ export interface IApp {
298
317
  * @param forceToLocal Force to local labels
299
318
  * @returns Message
300
319
  */
301
- formatResult(result: IActionResult, forceToLocal?: boolean): string;
320
+ formatResult(result: IActionResult, forceToLocal?: FormatResultCustomCallback): string;
302
321
  /**
303
322
  * Fresh countdown UI
304
323
  * @param callback Callback
@@ -439,6 +458,12 @@ export interface IApp {
439
458
  * @param options Options
440
459
  */
441
460
  navigate<T extends number | string | URL>(to: T, options?: T extends number ? never : NavigateOptions): void;
461
+ /**
462
+ * Notify user with success message
463
+ * @param message Success message
464
+ * @param callback Popup close callback
465
+ */
466
+ ok(callback?: NotificationReturn<void>, message?: NotificationContent<unknown>): void;
442
467
  /**
443
468
  * Callback where exit a page
444
469
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/appscript",
3
- "version": "1.4.96",
3
+ "version": "1.4.98",
4
4
  "description": "Applications shared TypeScript framework",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -52,17 +52,17 @@
52
52
  },
53
53
  "homepage": "https://github.com/ETSOO/AppScript#readme",
54
54
  "dependencies": {
55
- "@etsoo/notificationbase": "^1.1.42",
56
- "@etsoo/restclient": "^1.1.5",
57
- "@etsoo/shared": "^1.2.41",
55
+ "@etsoo/notificationbase": "^1.1.43",
56
+ "@etsoo/restclient": "^1.1.6",
57
+ "@etsoo/shared": "^1.2.42",
58
58
  "crypto-js": "^4.2.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@babel/cli": "^7.24.6",
62
- "@babel/core": "^7.24.6",
63
- "@babel/plugin-transform-runtime": "^7.24.6",
64
- "@babel/preset-env": "^7.24.6",
65
- "@babel/runtime-corejs3": "^7.24.6",
61
+ "@babel/cli": "^7.24.7",
62
+ "@babel/core": "^7.24.7",
63
+ "@babel/plugin-transform-runtime": "^7.24.7",
64
+ "@babel/preset-env": "^7.24.7",
65
+ "@babel/runtime-corejs3": "^7.24.7",
66
66
  "@types/crypto-js": "^4.2.2",
67
67
  "@types/jest": "^29.5.12",
68
68
  "jest": "^29.7.0",
@@ -32,6 +32,7 @@ import { IUser } from '../state/User';
32
32
  import { IAppSettings } from './AppSettings';
33
33
  import {
34
34
  appFields,
35
+ FormatResultCustomCallback,
35
36
  IApp,
36
37
  IAppFields,
37
38
  IDetectIPCallback,
@@ -752,17 +753,34 @@ export abstract class CoreApp<
752
753
  return [this.fields.headerToken];
753
754
  }
754
755
 
756
+ /**
757
+ * Alert result
758
+ * @param result Result message
759
+ * @param callback Callback
760
+ */
761
+ alertResult(result: string, callback?: NotificationReturn<void>): void;
762
+
755
763
  /**
756
764
  * Alert action result
757
- * @param result Action result or message
765
+ * @param result Action result
758
766
  * @param callback Callback
767
+ * @param forceToLocal Force to local labels
759
768
  */
769
+ alertResult(
770
+ result: IActionResult,
771
+ callback?: NotificationReturn<void>,
772
+ forceToLocal?: FormatResultCustomCallback
773
+ ): void;
774
+
760
775
  alertResult(
761
776
  result: IActionResult | string,
762
- callback?: NotificationReturn<void>
777
+ callback?: NotificationReturn<void>,
778
+ forceToLocal?: FormatResultCustomCallback
763
779
  ) {
764
780
  const message =
765
- typeof result === 'string' ? result : this.formatResult(result);
781
+ typeof result === 'string'
782
+ ? result
783
+ : this.formatResult(result, forceToLocal);
766
784
  this.notifier.alert(message, callback);
767
785
  }
768
786
 
@@ -1307,32 +1325,53 @@ export abstract class CoreApp<
1307
1325
  : result;
1308
1326
  }
1309
1327
 
1328
+ private getFieldLabel(field: string) {
1329
+ return this.get(field.formatInitial(false)) ?? field;
1330
+ }
1331
+
1310
1332
  /**
1311
1333
  * Format result text
1312
1334
  * @param result Action result
1313
1335
  * @param forceToLocal Force to local labels
1314
1336
  */
1315
- formatResult(result: IActionResult, forceToLocal?: boolean) {
1316
- const title = result.title;
1317
- if (title && /^\w+$/.test(title)) {
1318
- const key = title.formatInitial(false);
1319
- const localTitle = this.get(key);
1320
- if (localTitle) {
1321
- result.title = localTitle;
1322
-
1323
- // Hold the original title in type when type is null
1324
- if (result.type == null) result.type = title;
1325
- }
1326
- } else if ((title == null || forceToLocal) && result.type != null) {
1327
- // Get label from type
1328
- const key = result.type.formatInitial(false);
1329
- result.title = this.get(key);
1330
- }
1331
-
1332
- // When title contains {0}, replace with the field label
1333
- if (result.field && result.title?.includes('{0}')) {
1334
- const fieldLabel = this.get(result.field.formatInitial(false));
1335
- if (fieldLabel) result.title = result.title.format(fieldLabel);
1337
+ formatResult(
1338
+ result: IActionResult,
1339
+ forceToLocal?: FormatResultCustomCallback
1340
+ ) {
1341
+ // Destruct the result
1342
+ const { title, type, field } = result;
1343
+ const data = { title, type, field };
1344
+
1345
+ if (type === 'ItemExists' && field) {
1346
+ // Special case
1347
+ const fieldLabel =
1348
+ (typeof forceToLocal === 'function'
1349
+ ? forceToLocal(data)
1350
+ : undefined) ?? this.getFieldLabel(field);
1351
+ result.title = this.get('itemExists')?.format(fieldLabel);
1352
+ } else if (title?.includes('{0}')) {
1353
+ // When title contains {0}, replace with the field label
1354
+ const fieldLabel =
1355
+ (typeof forceToLocal === 'function'
1356
+ ? forceToLocal(data)
1357
+ : undefined) ?? (field ? this.getFieldLabel(field) : '');
1358
+
1359
+ result.title = title.format(fieldLabel);
1360
+ } else if (title && /^\w+$/.test(title)) {
1361
+ // When title is a single word
1362
+ // Hold the original title in type when type is null
1363
+ if (type == null) result.type = title;
1364
+ const localTitle =
1365
+ (typeof forceToLocal === 'function'
1366
+ ? forceToLocal(data)
1367
+ : undefined) ?? this.getFieldLabel(title);
1368
+ result.title = localTitle;
1369
+ } else if ((title == null || forceToLocal) && type != null) {
1370
+ const localTitle =
1371
+ (typeof forceToLocal === 'function'
1372
+ ? forceToLocal(data)
1373
+ : undefined) ?? this.getFieldLabel(type);
1374
+ result.title = localTitle;
1336
1375
  }
1337
1376
 
1338
1377
  return ActionResultError.format(result);
@@ -1645,6 +1684,16 @@ export abstract class CoreApp<
1645
1684
  }
1646
1685
  }
1647
1686
 
1687
+ /**
1688
+ * Notify user with success message
1689
+ * @param callback Popup close callback
1690
+ * @param message Success message
1691
+ */
1692
+ ok(callback?: NotificationReturn<void>, message?: NotificationContent<N>) {
1693
+ message ??= this.get('operationSucceeded')!;
1694
+ this.notifier.succeed(message, undefined, callback);
1695
+ }
1696
+
1648
1697
  /**
1649
1698
  * Callback where exit a page
1650
1699
  */
package/src/app/IApp.ts CHANGED
@@ -49,6 +49,22 @@ export type RefreshTokenResult =
49
49
  | ApiDataError
50
50
  | IActionResult;
51
51
 
52
+ /**
53
+ * Format result custom type
54
+ */
55
+ export type FormatResultCustom = {
56
+ title?: string;
57
+ type?: string;
58
+ field?: string;
59
+ };
60
+
61
+ /**
62
+ * Format result custom callback type
63
+ */
64
+ export type FormatResultCustomCallback =
65
+ | ((data: FormatResultCustom) => string | null | undefined)
66
+ | boolean;
67
+
52
68
  /**
53
69
  * Refresh token props
54
70
  */
@@ -201,14 +217,23 @@ export interface IApp {
201
217
  */
202
218
  addRootUrl(url: string): string;
203
219
 
220
+ /**
221
+ * Alert result
222
+ * @param result Result message
223
+ * @param callback Callback
224
+ */
225
+ alertResult(result: string, callback?: NotificationReturn<void>): void;
226
+
204
227
  /**
205
228
  * Alert action result
206
- * @param result Action result or message
229
+ * @param result Action result
207
230
  * @param callback Callback
231
+ * @param forceToLocal Force to local labels
208
232
  */
209
233
  alertResult(
210
- result: IActionResult | string,
211
- callback?: NotificationReturn<void>
234
+ result: IActionResult,
235
+ callback?: NotificationReturn<void>,
236
+ forceToLocal?: FormatResultCustomCallback
212
237
  ): void;
213
238
 
214
239
  /**
@@ -410,7 +435,10 @@ export interface IApp {
410
435
  * @param forceToLocal Force to local labels
411
436
  * @returns Message
412
437
  */
413
- formatResult(result: IActionResult, forceToLocal?: boolean): string;
438
+ formatResult(
439
+ result: IActionResult,
440
+ forceToLocal?: FormatResultCustomCallback
441
+ ): string;
414
442
 
415
443
  /**
416
444
  * Fresh countdown UI
@@ -596,6 +624,16 @@ export interface IApp {
596
624
  options?: T extends number ? never : NavigateOptions
597
625
  ): void;
598
626
 
627
+ /**
628
+ * Notify user with success message
629
+ * @param message Success message
630
+ * @param callback Popup close callback
631
+ */
632
+ ok(
633
+ callback?: NotificationReturn<void>,
634
+ message?: NotificationContent<unknown>
635
+ ): void;
636
+
599
637
  /**
600
638
  * Callback where exit a page
601
639
  */