From c851ab1394fae4863d60424792ea1f8f8f83b1a8 Mon Sep 17 00:00:00 2001 From: Dan Percic Date: Mon, 7 Aug 2023 15:24:06 +0300 Subject: [PATCH] escape html updates --- .eslintrc.js | 144 ++++++++++++++++++ .../circle-button/circle-button.component.ts | 24 +-- src/lib/utils/functions.ts | 60 ++++++-- 3 files changed, 206 insertions(+), 22 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 97f3363..ce21309 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,6 +49,150 @@ module.exports = { ], '@typescript-eslint/restrict-template-expressions': 'off', '@typescript-eslint/lines-between-class-members': 'off', + '@typescript-eslint/member-ordering': [ + 'warn', + { + default: [ + // Index signature + 'signature', + 'call-signature', + + // Fields + '#private-static-field', + 'private-static-field', + 'protected-static-field', + 'public-static-field', + + '#private-instance-field', + 'private-instance-field', + 'protected-instance-field', + 'public-instance-field', + + 'private-decorated-field', + 'protected-decorated-field', + 'public-decorated-field', + + 'protected-abstract-field', + 'public-abstract-field', + + '#private-field', + 'private-field', + 'protected-field', + 'public-field', + + 'static-field', + 'instance-field', + 'abstract-field', + + 'decorated-field', + + 'field', + + // Static initialization + 'static-initialization', + + // Constructors + 'public-constructor', + 'protected-constructor', + 'private-constructor', + + 'constructor', + + // Getters + 'public-static-get', + 'protected-static-get', + 'private-static-get', + '#private-static-get', + + 'public-decorated-get', + 'protected-decorated-get', + 'private-decorated-get', + + 'public-instance-get', + 'protected-instance-get', + 'private-instance-get', + '#private-instance-get', + + 'public-abstract-get', + 'protected-abstract-get', + + 'public-get', + 'protected-get', + 'private-get', + '#private-get', + + 'static-get', + 'instance-get', + 'abstract-get', + + 'decorated-get', + + 'get', + + // Setters + 'public-static-set', + 'protected-static-set', + 'private-static-set', + '#private-static-set', + + 'public-decorated-set', + 'protected-decorated-set', + 'private-decorated-set', + + 'public-instance-set', + 'protected-instance-set', + 'private-instance-set', + '#private-instance-set', + + 'public-abstract-set', + 'protected-abstract-set', + + 'public-set', + 'protected-set', + 'private-set', + '#private-set', + + 'static-set', + 'instance-set', + 'abstract-set', + + 'decorated-set', + + 'set', + + // Methods + 'public-static-method', + 'protected-static-method', + 'private-static-method', + '#private-static-method', + + 'public-decorated-method', + 'protected-decorated-method', + 'private-decorated-method', + + 'public-instance-method', + 'protected-instance-method', + 'private-instance-method', + '#private-instance-method', + + 'public-abstract-method', + 'protected-abstract-method', + + 'public-method', + 'protected-method', + 'private-method', + '#private-method', + + 'static-method', + 'instance-method', + 'abstract-method', + + 'decorated-method', + + 'method', + ], + }, + ], }, }, { diff --git a/src/lib/buttons/circle-button/circle-button.component.ts b/src/lib/buttons/circle-button/circle-button.component.ts index 1874f80..840379a 100644 --- a/src/lib/buttons/circle-button/circle-button.component.ts +++ b/src/lib/buttons/circle-button/circle-button.component.ts @@ -1,15 +1,15 @@ -import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, inject, Input, OnInit, Output, ViewChild } from '@angular/core'; -import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; -import { CircleButtonType, CircleButtonTypes } from '../types/circle-button.type'; -import { IqserTooltipPosition, IqserTooltipPositions, randomString } from '../../utils'; import { NgIf } from '@angular/common'; +import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, inject, Input, OnInit, Output, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; -import { StopPropagationDirective } from '../../directives'; +import { MatTooltip, MatTooltipModule } from '@angular/material/tooltip'; import { RouterLink } from '@angular/router'; +import { StopPropagationDirective } from '../../directives'; +import { IqserTooltipPosition, IqserTooltipPositions, randomString } from '../../utils'; +import { CircleButtonType, CircleButtonTypes } from '../types/circle-button.type'; @Component({ - selector: 'iqser-circle-button [icon]', + selector: 'iqser-circle-button', templateUrl: './circle-button.component.html', styleUrls: ['./circle-button.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, @@ -17,8 +17,12 @@ import { RouterLink } from '@angular/router'; imports: [MatTooltipModule, MatIconModule, NgIf, MatButtonModule, StopPropagationDirective], }) export class CircleButtonComponent implements OnInit { + readonly #elementRef = inject(ElementRef); + @ViewChild(MatTooltip) private readonly _matTooltip!: MatTooltip; + protected readonly _circleButtonTypes = CircleButtonTypes; + protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true }); @Input() buttonId = `${randomString()}-circle-button`; - @Input() icon!: string; + @Input({ required: true }) icon!: string; @Input() tooltip?: string; @Input() tooltipClass?: string; @Input() showDot = false; @@ -32,12 +36,8 @@ export class CircleButtonComponent implements OnInit { @Input() size = 34; @Input() iconSize = 14; @Output() readonly action = new EventEmitter(); - protected readonly _circleButtonTypes = CircleButtonTypes; - protected readonly _hasRouterLink = !!inject(RouterLink, { optional: true, host: true }); - @ViewChild(MatTooltip) private readonly _matTooltip!: MatTooltip; - readonly #elementRef = inject(ElementRef); - ngOnInit(): void { + ngOnInit() { this.#elementRef.nativeElement.style.setProperty('--circle-button-size', `${this.size}px`); this.#elementRef.nativeElement.style.setProperty('--circle-button-icon-size', `${this.iconSize}px`); } diff --git a/src/lib/utils/functions.ts b/src/lib/utils/functions.ts index 6f99ad6..da8ca29 100644 --- a/src/lib/utils/functions.ts +++ b/src/lib/utils/functions.ts @@ -1,9 +1,9 @@ -import type { Id, ITrackable } from '../listing'; -import { UntypedFormGroup } from '@angular/forms'; -import { forOwn, has, isEqual, isPlainObject, transform } from 'lodash-es'; -import dayjs, { type Dayjs } from 'dayjs'; import { inject } from '@angular/core'; +import { UntypedFormGroup } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; +import dayjs, { type Dayjs } from 'dayjs'; +import { forOwn, has, isEqual, isPlainObject, transform } from 'lodash-es'; +import type { Id, ITrackable } from '../listing'; export function capitalize(value: string | string): string { if (!value) { @@ -37,7 +37,49 @@ export function size(value: number): string { return `${(value / 1000).toFixed(2)} KB`; } +interface IReplaceOptions { + readonly searchValue: string | RegExp; + readonly replaceValue: string; +} + +const replacements: { searchValue: string; replaceValue: string }[] = [ + { searchValue: '&', replaceValue: '&' }, + { searchValue: ' ', replaceValue: ' ' }, + { searchValue: '<', replaceValue: '<' }, + { searchValue: '>', replaceValue: '>' }, + { searchValue: '"', replaceValue: '"' }, + { searchValue: "'", replaceValue: ''' }, +]; +const escapeReplacements: IReplaceOptions[] = replacements.map(({ searchValue, replaceValue }) => ({ + searchValue: new RegExp(searchValue, 'g'), + replaceValue, +})); +const unescapeReplacements: IReplaceOptions[] = replacements.map(({ searchValue, replaceValue }) => ({ + searchValue: new RegExp(replaceValue, 'g'), + replaceValue: searchValue, +})); + export function escapeHtml(unsafe: T, options?: { ignoreTags: string[] }) { + return replaceHtml(unsafe, { + ignoreTags: options?.ignoreTags, + replacements: escapeReplacements, + }); +} + +export function unescapeHtml(unsafe: T, options?: { ignoreTags: string[] }) { + return replaceHtml(unsafe, { + ignoreTags: options?.ignoreTags, + replacements: unescapeReplacements, + }); +} + +export function replaceHtml( + unsafe: T, + options: { + ignoreTags?: string[]; + replacements: IReplaceOptions[]; + }, +) { if (typeof unsafe !== 'string') { return unsafe; } @@ -57,12 +99,10 @@ export function escapeHtml(unsafe: T, options?: { ig _unsafe = _unsafe.replaceAll(key, value); }); - let escaped = _unsafe - .replaceAll(/&/g, '&') - .replaceAll(/ /g, ' ') - .replaceAll(//g, '>') - .replaceAll(/"/g, '"'); + let escaped = _unsafe; + for (const replacement of options.replacements) { + escaped = escaped.replaceAll(replacement.searchValue, replacement.replaceValue); + } if (ignoredTags) { Object.entries(ignoredTags).forEach(([key, value]) => {