@sumaris-net/ngx-components 1.2.8 → 1.3.3
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.
- package/bundles/sumaris-net.ngx-components.umd.js +51 -26
- package/bundles/sumaris-net.ngx-components.umd.js.map +1 -1
- package/bundles/sumaris-net.ngx-components.umd.min.js +1 -1
- package/bundles/sumaris-net.ngx-components.umd.min.js.map +1 -1
- package/esm2015/src/app/core/form/entity-editor.class.js +4 -4
- package/esm2015/src/app/core/form/form.class.js +2 -2
- package/esm2015/src/app/core/home/home.js +2 -2
- package/esm2015/src/app/core/install/install-upgrade-card.component.js +4 -4
- package/esm2015/src/app/core/services/platform.service.js +16 -16
- package/esm2015/src/app/core/table/table.class.js +2 -2
- package/esm2015/src/app/shared/pipes/form.pipes.js +2 -2
- package/esm2015/src/app/shared/validator/form-error-adapter.class.js +26 -2
- package/fesm2015/sumaris-net.ngx-components.js +49 -25
- package/fesm2015/sumaris-net.ngx-components.js.map +1 -1
- package/package.json +1 -1
- package/src/app/core/form/entity-editor.class.d.ts +1 -1
- package/src/app/core/install/install-upgrade-card.component.d.ts +1 -1
- package/src/app/core/services/platform.service.d.ts +1 -1
- package/src/app/shared/validator/form-error-adapter.class.d.ts +3 -1
- package/sumaris-net.ngx-components.metadata.json +1 -1
|
@@ -4138,7 +4138,7 @@
|
|
|
4138
4138
|
this.translate = translate;
|
|
4139
4139
|
this.errorI18nKeys = Object.assign(Object.assign({}, SharedValidators.I18N_ERROR_KEYS), errorI18nKeys);
|
|
4140
4140
|
}
|
|
4141
|
-
FormErrorTranslator.prototype.
|
|
4141
|
+
FormErrorTranslator.prototype.translateFormErrors = function (control, opts) {
|
|
4142
4142
|
var _this = this;
|
|
4143
4143
|
if (!control || !control.invalid)
|
|
4144
4144
|
return '';
|
|
@@ -4167,6 +4167,31 @@
|
|
|
4167
4167
|
return res + (res.length ? separator : '') + formError;
|
|
4168
4168
|
}, "");
|
|
4169
4169
|
};
|
|
4170
|
+
FormErrorTranslator.prototype.translateErrors = function (errors, opts) {
|
|
4171
|
+
var _this = this;
|
|
4172
|
+
var separator = opts && opts.separator || ', ';
|
|
4173
|
+
return errors && Object.keys(errors).reduce(function (res, path) {
|
|
4174
|
+
var pathErrors = errors[path];
|
|
4175
|
+
// Should be a control map of errors
|
|
4176
|
+
if (pathErrors) {
|
|
4177
|
+
// Try to convert the control path
|
|
4178
|
+
var i18nPath = _this.translateControlPath(path, opts);
|
|
4179
|
+
// OK, we have a field name: use it
|
|
4180
|
+
var columnErrors = Object.keys(pathErrors).map(function (errorKey) { return _this.translateError(errorKey, pathErrors[errorKey]); });
|
|
4181
|
+
if (isEmptyArray(columnErrors))
|
|
4182
|
+
return res;
|
|
4183
|
+
// Add separator
|
|
4184
|
+
if (res.length)
|
|
4185
|
+
res += separator;
|
|
4186
|
+
return res + i18nPath + ": " + columnErrors.join(separator);
|
|
4187
|
+
}
|
|
4188
|
+
// Or try as global form error
|
|
4189
|
+
var formError = _this.translateError(path, pathErrors);
|
|
4190
|
+
if (isNil(formError))
|
|
4191
|
+
return res;
|
|
4192
|
+
return res + (res.length ? separator : '') + formError;
|
|
4193
|
+
}, "");
|
|
4194
|
+
};
|
|
4170
4195
|
FormErrorTranslator.prototype.translateControlPath = function (path, opts) {
|
|
4171
4196
|
if (opts === null || opts === void 0 ? void 0 : opts.controlPathTranslator) {
|
|
4172
4197
|
return opts === null || opts === void 0 ? void 0 : opts.controlPathTranslator.translateControlPath(path);
|
|
@@ -4241,7 +4266,7 @@
|
|
|
4241
4266
|
};
|
|
4242
4267
|
FormErrorTranslatePipe.prototype._updateValue = function (form, opts, status) {
|
|
4243
4268
|
if (status === 'INVALID' || form.invalid) {
|
|
4244
|
-
this.value = this.adapter.
|
|
4269
|
+
this.value = this.adapter.translateFormErrors(form, opts);
|
|
4245
4270
|
this._ref.markForCheck();
|
|
4246
4271
|
}
|
|
4247
4272
|
else if (this.value !== undefined) {
|
|
@@ -16762,11 +16787,11 @@
|
|
|
16762
16787
|
return !this.platform.is('mobile') || this.platform.is('mobileweb');
|
|
16763
16788
|
};
|
|
16764
16789
|
PlatformService.prototype.isAndroidCordova = function () {
|
|
16765
|
-
return this._android && this._cordova;
|
|
16790
|
+
return this._android && this._cordova || false;
|
|
16766
16791
|
};
|
|
16767
16792
|
Object.defineProperty(PlatformService.prototype, "canDownload", {
|
|
16768
16793
|
get: function () {
|
|
16769
|
-
return isNotNil(this.downloader);
|
|
16794
|
+
return this._android && this._cordova && isNotNil(this.downloader);
|
|
16770
16795
|
},
|
|
16771
16796
|
enumerable: false,
|
|
16772
16797
|
configurable: true
|
|
@@ -16786,7 +16811,7 @@
|
|
|
16786
16811
|
};
|
|
16787
16812
|
PlatformService.prototype.ngOnStart = function () {
|
|
16788
16813
|
return __awaiter(this, void 0, void 0, function () {
|
|
16789
|
-
var now,
|
|
16814
|
+
var now, storage, err_1;
|
|
16790
16815
|
var _this = this;
|
|
16791
16816
|
return __generator(this, function (_b) {
|
|
16792
16817
|
switch (_b.label) {
|
|
@@ -16814,8 +16839,8 @@
|
|
|
16814
16839
|
_b.sent();
|
|
16815
16840
|
return [4 /*yield*/, this.storageReady()];
|
|
16816
16841
|
case 3:
|
|
16817
|
-
|
|
16818
|
-
return [4 /*yield*/, this.migrateStorage(
|
|
16842
|
+
storage = _b.sent();
|
|
16843
|
+
return [4 /*yield*/, this.migrateStorage(storage)];
|
|
16819
16844
|
case 4:
|
|
16820
16845
|
_b.sent();
|
|
16821
16846
|
// Start root services
|
|
@@ -17007,28 +17032,28 @@
|
|
|
17007
17032
|
});
|
|
17008
17033
|
});
|
|
17009
17034
|
};
|
|
17010
|
-
PlatformService.prototype.migrateStorage = function (
|
|
17035
|
+
PlatformService.prototype.migrateStorage = function (storage) {
|
|
17011
17036
|
return __awaiter(this, void 0, void 0, function () {
|
|
17012
17037
|
var canMigrate, oldForage, keys, now, toast_1, duration, err_2;
|
|
17013
17038
|
return __generator(this, function (_b) {
|
|
17014
17039
|
switch (_b.label) {
|
|
17015
17040
|
case 0:
|
|
17016
|
-
canMigrate =
|
|
17017
|
-
((
|
|
17018
|
-
(
|
|
17041
|
+
canMigrate = storage &&
|
|
17042
|
+
((storage.driver() === 'cordovaSQLiteDriver' && (storage.supports(storage.INDEXEDDB) || storage.supports(storage.WEBSQL))) ||
|
|
17043
|
+
(storage.driver() === storage.WEBSQL && storage.supports(storage.INDEXEDDB)));
|
|
17019
17044
|
if (!canMigrate)
|
|
17020
17045
|
return [2 /*return*/]; // Skip
|
|
17021
|
-
oldForage =
|
|
17022
|
-
name:
|
|
17023
|
-
storeName:
|
|
17024
|
-
driver: [
|
|
17046
|
+
oldForage = storage.createInstance({
|
|
17047
|
+
name: storage.config().name,
|
|
17048
|
+
storeName: storage.config().storeName,
|
|
17049
|
+
driver: [storage.INDEXEDDB, storage.WEBSQL]
|
|
17025
17050
|
});
|
|
17026
17051
|
return [4 /*yield*/, oldForage.keys()];
|
|
17027
17052
|
case 1:
|
|
17028
17053
|
keys = _b.sent();
|
|
17029
17054
|
if (!isEmptyArray(keys)) return [3 /*break*/, 3];
|
|
17030
17055
|
// Drop the old instance (not need anymore)
|
|
17031
|
-
console.debug("[platform] Old storage is empty: dropping unused instance {name: '" +
|
|
17056
|
+
console.debug("[platform] Old storage is empty: dropping unused instance {name: '" + storage.config().name + "', driver: '" + oldForage.driver() + "'}");
|
|
17032
17057
|
return [4 /*yield*/, oldForage.dropInstance()];
|
|
17033
17058
|
case 2:
|
|
17034
17059
|
_b.sent();
|
|
@@ -17043,7 +17068,7 @@
|
|
|
17043
17068
|
_b.label = 5;
|
|
17044
17069
|
case 5:
|
|
17045
17070
|
_b.trys.push([5, 9, , 12]);
|
|
17046
|
-
return [4 /*yield*/, StorageUtils.copy(oldForage,
|
|
17071
|
+
return [4 /*yield*/, StorageUtils.copy(oldForage, storage, { keys: keys, deleteAfterCopy: false })];
|
|
17047
17072
|
case 6:
|
|
17048
17073
|
_b.sent();
|
|
17049
17074
|
duration = Date.now() - now;
|
|
@@ -17056,7 +17081,7 @@
|
|
|
17056
17081
|
_b.sent();
|
|
17057
17082
|
console.info('[platform] Starting storage migration [OK]');
|
|
17058
17083
|
// Drop the old instance
|
|
17059
|
-
console.info("[platform] Drop old storage {name: '" +
|
|
17084
|
+
console.info("[platform] Drop old storage {name: '" + storage.config().name + "', driver: '" + oldForage.driver() + "'}");
|
|
17060
17085
|
return [4 /*yield*/, oldForage.dropInstance()];
|
|
17061
17086
|
case 8:
|
|
17062
17087
|
_b.sent();
|
|
@@ -19578,7 +19603,7 @@
|
|
|
19578
19603
|
AppForm.prototype.getFormError = function (form, opts) {
|
|
19579
19604
|
if (!form || !this.errorTranslator)
|
|
19580
19605
|
return undefined;
|
|
19581
|
-
return this.errorTranslator.
|
|
19606
|
+
return this.errorTranslator.translateFormErrors(this.form, Object.assign({ controlPathTranslator: this }, opts));
|
|
19582
19607
|
};
|
|
19583
19608
|
AppForm.prototype.getI18nFieldName = function (path) {
|
|
19584
19609
|
return (this.i18nFieldPrefix || '') + changeCaseToUnderscore(path).toUpperCase();
|
|
@@ -20185,7 +20210,7 @@
|
|
|
20185
20210
|
{ type: i0.Component, args: [{
|
|
20186
20211
|
moduleId: module.id.toString(),
|
|
20187
20212
|
selector: 'app-page-home',
|
|
20188
|
-
template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\"> {{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p>Loading...</p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\"> {{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n showInstallButton=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n </ion-card-content>\n\n <ion-footer class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}} </ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\"> </p>-->\n </ng-container>\n\n <ion-button expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </ion-footer>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon> {{ page.time|dateFromNow }}</small> \n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [
|
|
20213
|
+
template: "<app-toolbar [canGoBack]=\"false\"\n visible-xs visible-sm visible-mobile>\n\n <!-- change locale button -->\n <ion-button slot=\"end\" *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\"> {{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n</app-toolbar>\n\n<!-- change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <button *ngFor=\"let item of locales\"\n mat-menu-item\n (click)=\"changeLanguage(item.key)\">\n <ion-label>{{item.value}}</ion-label>\n </button>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" no-padding-xs>\n\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!loading\">\n <div class=\"spinner\" *ngIf=\"loading && showSpinner\">\n <p>Loading...</p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n <div hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n <!-- change locale button -->\n <ion-button color=\"secondary\" fill=\"solid\"\n *ngIf=\"currentLocaleCode; let code\"\n [matMenuTriggerFor]=\"localeMenu\">\n <ion-label class=\"ion-text-uppercase\"> {{ code }}</ion-label>\n <mat-icon slot=\"end\">arrow_drop_down</mat-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\"\n showInstallButton=\"true\">\n </app-install-upgrade-card>\n\n <!-- Welcome card -->\n <ion-card *ngIf=\"!loading\"\n class=\"main welcome ion-padding ion-text-center ion-align-self-center\"\n @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB'|translate: {appName: appName}\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP'|translate: {appName: appName}\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\">\n </ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img class=\"logo\" *ngIf=\"logo\" src=\"{{ logo }}\">\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br/>\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n </ion-card-content>\n\n <ion-footer class=\"ion-padding-top\">\n\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons;\">\n\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n\n <!-- Feature buttons -->\n <ng-container *ngIf=\"$filteredButtons | async as buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button *ngIf=\"item.path\"\n expand=\"block\" color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">{{item.matIcon}}</mat-icon>\n <ion-text>{{'HOME.BTN_DATA_ENTRY'|translate: {name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{item.title}} </ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\"> </p>-->\n </ng-container>\n\n <ion-button expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: {displayName: accountName }\"></ion-text>\n <br/>\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n\n\n </ng-template>\n\n </ion-footer>\n </ion-card>\n\n <!-- Page history -->\n <ion-grid *ngIf=\"!loading && isLogin && pageHistory.length; else bottomBanner\"\n class=\"history-container ion-align-self-center\">\n <ion-row>\n <ion-col size=\"12\" size-xl=\"\" *ngFor=\"let page of pageHistory; trackBy: getPagePath\"\n class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button type=\"button\" tabindex=\"-1\"\n (click)=\"settings.removePageHistory(page.path)\"\n mat-icon-button class=\"ion-float-start ion-no-margin\">\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle|translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time|dateFormat:{time: true}\">\n <small><ion-icon name=\"time-outline\"></ion-icon> {{ page.time|dateFromNow }}</small> \n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item detail=\"true\"\n tappable class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\" lines=\"none\">\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{page.matIcon}}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n <ion-card-content class=\"ion-no-padding ion-padding-start\" *ngIf=\"page.children?.length\">\n <ion-list class=\"ion-no-padding\">\n <ion-item detail=\"true\"\n *ngFor=\"let childPage of page.children\"\n tappable class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\">\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">{{childPage.matIcon}}</mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n </ion-list>\n </ion-card-content>\n </ion-card>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Display configured Departments logos -->\n <ng-template #bottomBanner>\n <div class=\"bottom-banner ion-text-center ion-padding\" *ngIf=\"!loadingBanner\" @fadeInAnimation>\n <a href=\"{{ item.siteUrl }}\" *ngFor=\"let item of $partners | async \">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{item.label}}\" [title]=\"item.label\" />\n </a>\n </div>\n </ng-template>\n\n\n</ion-content>\n",
|
|
20189
20214
|
changeDetection: i0.ChangeDetectionStrategy.OnPush,
|
|
20190
20215
|
animations: [fadeInAnimation, slideUpDownAnimation],
|
|
20191
20216
|
styles: ["h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background:transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;display:inline-block;padding:15px}ion-content,ion-content .loading-page{background-color:var(--ion-color-primary)}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:hsla(0,0%,100%,.7)}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:hsla(0,0%,100%,.7)}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{--ion-item-background:$transparent;--ion-item-icon-color:var(--ion-color-primary);--ion-item-text-color:var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;-webkit-margin-end:16px!important;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background:var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color:var(--ion-color-dark);--ion-item-text-color:var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;-webkit-margin-end:16px!important;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{background-color:hsla(0,0%,100%,.7);min-width:240px;z-index:0;position:absolute;left:16px;right:16px;bottom:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content ion-text{text-align:center}@media screen and (max-width:575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card button{margin-top:8px}ion-content .bottom-banner{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width:767px) and (min-width:576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width:768px){ion-content{min-height:calc(100% - 100px)}}"]
|
|
@@ -21847,7 +21872,7 @@
|
|
|
21847
21872
|
this._subscription = new rxjs.Subscription();
|
|
21848
21873
|
this._showUpdateOfflineFeature = false;
|
|
21849
21874
|
this.loading = true;
|
|
21850
|
-
this.
|
|
21875
|
+
this.hasDownloader = false;
|
|
21851
21876
|
this.downloading = false;
|
|
21852
21877
|
this.waitingNetwork = false;
|
|
21853
21878
|
this.showUpgradeWarning = true;
|
|
@@ -21999,7 +22024,7 @@
|
|
|
21999
22024
|
if (!config)
|
|
22000
22025
|
return [2 /*return*/]; // Skip
|
|
22001
22026
|
console.info('[install-upgrade-card] Check if need to install or upgrade...');
|
|
22002
|
-
this.
|
|
22027
|
+
this.hasDownloader = this.platform.canDownload;
|
|
22003
22028
|
_a.label = 1;
|
|
22004
22029
|
case 1:
|
|
22005
22030
|
_a.trys.push([1, , 5, 6]);
|
|
@@ -22252,7 +22277,7 @@
|
|
|
22252
22277
|
AppInstallUpgradeCard.decorators = [
|
|
22253
22278
|
{ type: i0.Component, args: [{
|
|
22254
22279
|
selector: 'app-install-upgrade-card',
|
|
22255
|
-
template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Update offline feature card-->\n<ion-card *ngIf=\"showUpdateOfflineFeature && !loading && !offline\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateOfflineModeClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.filename; else redirectButton\">\n\n <ng-container *ngIf=\"
|
|
22280
|
+
template: "\n\n<!-- Offline mode card-->\n<ion-card *ngIf=\"showOfflineWarning && (!loading && offline || waitingNetwork)\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\" *ngIf=\"!waitingNetwork; else waitingNetworkText\">\n <h4>\n <b *ngIf=\"isLogin; else notLogin\" [innerHTML]=\"'NETWORK.INFO.OFFLINE_OR_UNAUTHORIZED'| translate\"></b>\n <ng-template #notLogin>\n <b [innerHTML]=\"'NETWORK.INFO.OFFLINE'| translate\"></b>\n </ng-template>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.OFFLINE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n <ng-template #waitingNetworkText>\n <ion-text class=\"ion-text-wrap\" [innerHTML]=\"'NETWORK.INFO.RETRY_TO_CONNECT'| translate\"></ion-text>\n </ng-template>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button *ngIf=\"!waitingNetwork; else waitingSpinner\" color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"tryOnline()\">\n <span translate>NETWORK.BTN_CHECK_ALIVE</span>\n </ion-button>\n\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Upgrade links -->\n<ion-card *ngIf=\"showUpgradeWarning && !loading && !offline && upgradeLinks\"\n color=\"accent\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of upgradeLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3>\n <span *ngIf=\"link.version\" [innerHTML]=\"'INFO.UPDATE_APP_TO_VERSION'| translate: link\"></span>\n <span *ngIf=\"!link.version\" [innerHTML]=\"'INFO.UPDATE_APP'| translate: link\"></span>\n </h3>\n <h4>\n <small *ngIf=\"last\" [innerHTML]=\"'INFO.UPDATE_APP_HELP'|translate\"></small>\n </h4>\n </ion-text>\n </ion-col>\n\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Install links -->\n<ion-card *ngIf=\"showInstallButton && !loading && !offline && installLinks\"\n color=\"secondary\"\n class=\"ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row *ngFor=\"let link of installLinks; last as last\">\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h3 [innerHTML]=\"'INFO.DOWNLOAD_APP_TITLE'| translate: {name: link.name, platform: getPlatformName(link.platform) }\"></h3>\n <h4><small *ngIf=\"last\" [innerHTML]=\"'INFO.DOWNLOAD_APP_HELP'|translate\"></small></h4>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n <ng-container *ngTemplateOutlet=\"downloadButton; context: { $implicit: link }\"></ng-container>\n </ion-col>\n </ion-row>\n\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Update offline feature card-->\n<ion-card *ngIf=\"showUpdateOfflineFeature && !loading && !offline\"\n color=\"accent\"\n class=\"main ion-no-margin\"\n @slideUpDownAnimation>\n <ion-card-content class=\"ion-no-padding\">\n <ion-grid>\n <ion-row>\n <ion-col>\n <ion-text class=\"ion-text-wrap\">\n <h4>\n <b [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE'| translate\"></b>\n </h4>\n <h3>\n <small [innerHTML]=\"'NETWORK.INFO.UPDATE_OFFLINE_MODE_HELP'|translate\"></small>\n </h3>\n </ion-text>\n </ion-col>\n <ion-col size=\"auto\">\n\n <!-- Retry button -->\n <ion-button color=\"tertiary\" class=\"ion-float-end\"\n (click)=\"onUpdateOfflineModeClick.emit($event)\">\n <span translate>NETWORK.BTN_UPDATE</span>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n </ion-card-content>\n</ion-card>\n\n<!-- Display a download button, depending on the link URL, and the device platform -->\n<ng-template #downloadButton let-link>\n\n <ng-container *ngIf=\"link.filename; else redirectButton\">\n\n <ng-container *ngIf=\"hasDownloader; else webDownloadButton\">\n <!-- Download using platform -->\n <ion-button (click)=\"download($event, asLink(link))\"\n *ngIf=\"!link.location; else openButton\"\n [disabled]=\"link.downloading\"\n color=\"tertiary\" class=\"ion-float-end\">\n <ion-icon slot=\"start\" *ngIf=\"!link.downloading\" name=\"download\"></ion-icon>\n <ion-spinner slot=\"start\" class=\"ion-no-padding\" *ngIf=\"link.downloading\"></ion-spinner>\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n\n <!-- Open APK button -->\n <ng-template #openButton>\n <ion-button *ngIf=\"link.location\"\n (click)=\"openFile($event, link)\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_INSTALL</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web download button -->\n <ng-template #webDownloadButton>\n <ion-button [download]=\"link.downloadFilename\"\n [href]=\"link.url\"\n color=\"tertiary\" class=\"ion-float-end\" >\n <ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>\n </ion-button>\n </ng-template>\n </ng-container>\n\n <!-- Web redirect button -->\n <ng-template #redirectButton>\n <ion-button [href]=\"link.url\" rel=\"external\" color=\"tertiary\" class=\"ion-float-end\" target=\"_blank\">\n <ion-label translate>COMMON.BTN_SHOW_MORE</ion-label>\n </ion-button>\n </ng-template>\n</ng-template>\n\n\n<!-- Waiting spinner -->\n<ng-template #waitingSpinner>\n <ion-spinner color=\"light\"></ion-spinner>\n</ng-template>\n",
|
|
22256
22281
|
changeDetection: i0.ChangeDetectionStrategy.OnPush,
|
|
22257
22282
|
animations: [slideUpDownAnimation],
|
|
22258
22283
|
styles: ["ion-text small{font-size:85%}"]
|
|
@@ -25479,7 +25504,7 @@
|
|
|
25479
25504
|
row = row || this.editedRow;
|
|
25480
25505
|
if (!row || !this.formErrorAdapter)
|
|
25481
25506
|
return undefined;
|
|
25482
|
-
return this.formErrorAdapter.
|
|
25507
|
+
return this.formErrorAdapter.translateFormErrors(row.validator, Object.assign(Object.assign({}, this.errorTranslatorOptions), opts));
|
|
25483
25508
|
};
|
|
25484
25509
|
AppTable.prototype.setError = function (value, opts) {
|
|
25485
25510
|
if (this.errorSubject.value !== value) {
|
|
@@ -26404,7 +26429,7 @@
|
|
|
26404
26429
|
enableListenChanges: (injector.get(ENVIRONMENT).listenRemoteChanges === true), pathIdAttribute: 'id', autoLoad: true, autoUpdateRoute: true, i18nPrefix: ''
|
|
26405
26430
|
}, options);
|
|
26406
26431
|
_this.settings = injector.get(LocalSettingsService);
|
|
26407
|
-
_this.
|
|
26432
|
+
_this.errorTranslator = injector.get(FormErrorTranslator);
|
|
26408
26433
|
_this.cd = injector.get(i0.ChangeDetectorRef);
|
|
26409
26434
|
_this.dateFormat = injector.get(DateFormatPipe);
|
|
26410
26435
|
_this.toastController = injector.get(i1$5.ToastController);
|
|
@@ -26966,7 +26991,7 @@
|
|
|
26966
26991
|
var userMessage = err && err.message && this.translate.instant(err.message) || err;
|
|
26967
26992
|
// Add details error (if any) under the main message
|
|
26968
26993
|
var detailMessage = err && err.details && (err.details.message || err.details) || undefined;
|
|
26969
|
-
if (detailMessage) {
|
|
26994
|
+
if (isNotNilString(detailMessage)) {
|
|
26970
26995
|
userMessage += "<br/><small class=\"hidden-xs hidden-sm\" title=\"" + detailMessage + "\">";
|
|
26971
26996
|
userMessage += detailMessage.length < 70 ? detailMessage : detailMessage.substring(0, 67) + '...';
|
|
26972
26997
|
userMessage += '</small>';
|