diff --git a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html index 614b372..0cf0a5a 100644 --- a/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html +++ b/src/lib/dialog/confirmation-dialog/confirmation-dialog.component.html @@ -1,7 +1,5 @@
-
- {{ config.title }} -
+
diff --git a/src/lib/services/toaster.service.ts b/src/lib/services/toaster.service.ts index 03d6f55..1662d69 100644 --- a/src/lib/services/toaster.service.ts +++ b/src/lib/services/toaster.service.ts @@ -6,7 +6,6 @@ import { TranslateService } from '@ngx-translate/core'; import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http'; import { filter } from 'rxjs/operators'; import { ErrorMessageService } from './error-message.service'; -import { stripHtml } from 'string-strip-html'; import { DomSanitizer } from '@angular/platform-browser'; const enum NotificationType { @@ -80,9 +79,7 @@ export class Toaster { notificationType = NotificationType.INFO, options?: Partial, ): ActiveToast { - const params = options?.params ? this._sanitizeParams(options.params) : undefined; - - const translatedMsg = this._translateService.instant(message, params) as string; + const translatedMsg = this._translateService.instant(message, options?.params) as string; switch (notificationType) { case NotificationType.SUCCESS: @@ -94,11 +91,4 @@ export class Toaster { return this._toastr.info(translatedMsg, options?.title, options); } } - - private _sanitizeParams(params: Record): Record { - return Object.entries(params).reduce((acc, [key, value]) => { - acc[key] = stripHtml(value?.toString() ?? '').result; - return acc; - }, {} as Record); - } } diff --git a/src/lib/translations/iqser-translate-parser.service.ts b/src/lib/translations/iqser-translate-parser.service.ts new file mode 100644 index 0000000..973f5de --- /dev/null +++ b/src/lib/translations/iqser-translate-parser.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; +import { TranslateDefaultParser } from '@ngx-translate/core'; +import { escapeHtml } from '../utils'; + +@Injectable() +export class IqserTranslateParser extends TranslateDefaultParser { + interpolate(expr: any, params?: Record) { + const entries = Object.entries(params ?? {}); + const escapedParams = entries.reduce((acc, [key, value]) => ({ ...acc, [key]: escapeHtml(value) }), {}); + return super.interpolate(expr, escapedParams); + } +} diff --git a/src/lib/translations/iqser-translate.module.ts b/src/lib/translations/iqser-translate.module.ts index effcc13..a32ee3f 100644 --- a/src/lib/translations/iqser-translate.module.ts +++ b/src/lib/translations/iqser-translate.module.ts @@ -1,8 +1,9 @@ import { Inject, ModuleWithProviders, NgModule, Optional } from '@angular/core'; -import { TranslateCompiler, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateCompiler, TranslateLoader, TranslateModule, TranslateParser } from '@ngx-translate/core'; import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler'; import { pruningTranslationLoaderFactory } from './http-loader-factory'; import { IqserTranslateModuleOptions } from './iqser-translate-module-options'; +import { IqserTranslateParser } from './iqser-translate-parser.service'; const translateLoaderToken = 'translateLoader'; @@ -17,6 +18,10 @@ const translateLoaderToken = 'translateLoader'; provide: TranslateCompiler, useClass: TranslateMessageFormatCompiler, }, + parser: { + provide: TranslateParser, + useClass: IqserTranslateParser, + }, }), ], exports: [TranslateModule], @@ -30,6 +35,7 @@ export class IqserTranslateModule { static forRoot(options?: IqserTranslateModuleOptions): ModuleWithProviders { const pathPrefix = options?.pathPrefix?.length ? options.pathPrefix : '/assets/i18n/'; + return { ngModule: IqserTranslateModule, providers: [ diff --git a/src/lib/utils/functions.ts b/src/lib/utils/functions.ts index f6dbf18..f69a134 100644 --- a/src/lib/utils/functions.ts +++ b/src/lib/utils/functions.ts @@ -1,4 +1,4 @@ -import { Id, ITrackable } from '../listing/models/trackable'; +import { Id, ITrackable } from '../listing'; import { UntypedFormGroup } from '@angular/forms'; import { forOwn, has, isEqual, isPlainObject, transform } from 'lodash-es'; import dayjs, { Dayjs } from 'dayjs'; @@ -25,6 +25,42 @@ export function humanizeCamelCase(value: string): string { return value.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase(); } +export function escapeHtml(unsafe: T, options?: { ignoreTags: string[] }) { + if (typeof unsafe !== 'string') { + return unsafe; + } + + let _unsafe = unsafe as string; + + const ignoredTags = options?.ignoreTags?.reduce( + (acc, tag) => ({ + ...acc, + [`<${tag}>`]: `???${tag};`, + [``]: `???/${tag};`, + }), + {} as Record, + ); + + Object.entries(ignoredTags ?? {}).forEach(([key, value]) => { + _unsafe = _unsafe.replaceAll(key, value); + }); + + let escaped = _unsafe + .replaceAll(/&/g, '&') + .replaceAll(/ /g, ' ') + .replaceAll(//g, '>') + .replaceAll(/"/g, '"'); + + if (ignoredTags) { + Object.entries(ignoredTags).forEach(([key, value]) => { + escaped = escaped.replaceAll(value, key); + }); + } + + return escaped; +} + export function _log(value: unknown, message = '') { console.log(`%c[${dayjs().format('mm:ss.SSS')}] ${message}`, 'color: yellow;', value); }